Devicetree
 help / color / mirror / Atom feed
* [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607
@ 2026-11-02  9:25 Radu Sabau via B4 Relay
  2026-11-02  9:25 ` [PATCH v5 1/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
                   ` (7 more replies)
  0 siblings, 8 replies; 13+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02  9:25 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron

This series adds support for the Analog Devices ADIS16607, a precision
MEMS IMU with triaxial gyroscope, triaxial accelerometer, and
temperature sensor, communicating over a non-standard SPI Half-Duplex
protocol.

Patch 1 fixes the reset dispatch: both __adis_initial_startup()'s reset
fallback and the public adis_reset() wrapper were calling
__adis_reset() directly instead of dispatching through
adis->ops->reset(). That bug is invisible for every existing adislib
driver (adis_default_ops.reset is __adis_reset() itself), but silently
breaks a driver with fully custom ops - like this one - on any board
without a reset-gpios property, or any caller of adis_reset() on a
device like adis16550 whose custom reset op needs a different write
size than __adis_reset()'s hardcoded 8-bit one. Carries a Fixes tag for
the commit that introduced the hardcoded call; no stable backport is
needed since the bug only manifests for a custom-ops driver, and this
is the first one.

Patch 2 adds an optional self_test callback to adislib, for devices
whose self-test sequence doesn't fit the generic
mask-write/sleep/check-status pattern.

Patch 3 adds an optional init callback to adislib, for devices that
need extra device-specific bring-up between reset and self-test (SPI
mode locking, status/bootloader checks, pin or burst-mode
configuration, etc.) - not all ADIS devices need this, so it's only
called when set, same as self_test.

Patch 4 adds the devicetree binding.

Patches 5-8 add the driver in functional groupings, as requested in
v1 review:
 - Patch 5: base driver - probe, raw channel reads, decimation/sample
   frequency, startup and self-test via adislib, debugfs identity
   entries. No buffered capture yet.
 - Patch 6: buffered capture via the device's hardware FIFO.
 - Patch 7: external clock sync support.
 - Patch 8: calibration bias support for gyro/accel.

Notable points for reviewers:

 - The device does not implement the standard ADIS SPI protocol used
   by other parts in the family; reads/writes are done over a custom
   Half-Duplex sequence (see adis16607_spi_read()/adis16607_spi_write()
   and the adis_ops overrides). Half-Duplex is also what gives access
   to the device's burst read, which pops or reads a whole set of
   samples in a single CS-held transfer.

 - Startup is split across the two new adislib callbacks rather than a
   driver-private initial_startup(): adis16607_init() does SPI
   Half-Duplex locking, dev_id verification, the bootloader-busy
   check, and (once FIFO is wired up in patch 6) burst-mode and
   DR/DEV_NOK interrupt-pin configuration; adis16607_self_test() does
   the actual per-axis delta-check, clearing the SELF_TEST register on
   every exit path (not just success) so a failure doesn't leave the
   sensor sitting in its self-test/force state.

 - GPIO0's reset-pin routing (RESET_GPIO_MASK in USER_GPIO_CFG) is
   runtime-writable on this part, so a prior write (bug, firmware,
   direct register access) could leave it de-routed, silently
   defeating reset-gpios. Patch 5's probe() forces it back to the
   reset function over SPI before relying on toggling the pin -
   warning rather than erroring out on failure, so a still-responsive
   device still gets a real chance at reset. Patch 6 adds the
   DR/DEV_NOK pins to the same USER_GPIO_CFG register via a
   read-modify-write, so it can't clobber what probe() already set.

 - The hardware FIFO is the *only* buffered capture path - there is no
   single-sample direct burst-read trigger mode. The device offers its
   burst read on two registers: DIAG_STAT returns a single set of the
   most recent samples directly from the sensor, bypassing the FIFO,
   while FIFO_DATA pops one set from the FIFO. They are genuinely
   different commands with different payload shapes, so the FIFO pop
   stays driver-private rather than going through adislib's generic
   burst-read helpers.

 - The FIFO drain in the trigger handler reads DIAG_STAT (which
   clears FIFO_THRESH_MET as a side effect) and pops however many
   complete samples are available - no disabling, flushing, or
   re-enabling around the drain, since the datasheet's dual-port FIFO
   architecture means reads and writes don't contend. The FIFO is
   flushed and DIAG_STAT cleared once, when the buffer is disabled, so
   that the next session starts clean.

 - The FIFO-threshold interrupt is signaled via DEV_NOK on the
   device's one physically-wired interrupt pin, which stays asserted
   (level-triggered) until DIAG_STAT is read and cleared - this sets
   has_fifo on adis_data and requests the IRQ through adislib's
   threaded/level path, matching adislib's actual contract for that
   flag. probe() rejects any other interrupt type.

 - An optional external clock (clocks property) selects between
   SYNC_DIRECT and SYNC_SCALED modes depending on the supplied
   frequency, with the internal 8 kHz clock used as the default when
   no clock is provided (adis16607_config_clk()). The clock rate is
   requested as exclusive. In scaled mode the sync scale is picked
   when sampling_frequency is set; as that and the decimation can't
   change while capturing, such writes return -EBUSY with the buffer
   enabled.

This has been tested on a Raspberry Pi 4 (arm64) with the device wired
over SPI, exercising the FIFO buffered-capture path with the updated
DEV_NOK/level-triggered interrupt wiring.

To: Lars-Peter Clausen <lars@metafoo.de>
To: Michael Hennerich <Michael.Hennerich@analog.com>
To: Jonathan Cameron <jic23@kernel.org>
To: David Lechner <dlechner@baylibre.com>
To: Nuno Sá <nuno.sa@analog.com>
To: Andy Shevchenko <andy@kernel.org>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Alexandru Ardelean <alexandru.ardelean@analog.com>
Cc: linux-iio@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: Radu Sabau <radu.sabau@analog.com>

Changes in v5:
- Reordered the adislib patches so the reset-dispatch fix comes first
  and the self_test callback second (Nuno).
- Renamed the post_reset_setup callback to init (Jonathan).
- Dropped Rob Herring's Reviewed-by tag on the dt-bindings patch - the
  binding's GPIO-handling description changed substantially since v3
  (Jonathan).
- Base driver: unit-aware names (int_clk_Hz, clk_freq_mHz) and
  MILLIHZ_PER_HZ/MICROHZ_PER_HZ instead of bare 1000s; the sample
  period is now computed through a u64 temporary, so it stays correct
  on 32-bit; GENMASK()/FIELD_PREP()/FIELD_GET() in the register
  accessors, with a firmware revision struct and a shift-accumulated
  serial number; unsigned loop counters scoped to their loops;
  MODULE_DESCRIPTION() matching Kconfig; reworked channel macro
  indentation and wrapped over-long comments (Andy).
- Base driver: dev_err_probe() for probe-only errors, dropped the log
  lines duplicating what __adis_check_status() already prints, min()
  instead of min_t(), removed stray blank lines in read_raw(), dropped
  the protocol comment above adis16607_spi_read() and the redundant
  bits_per_word/tx zeroing, get_freq() uses the locked helper and
  set_freq() takes the lock only around the register writes (Nuno).
- Base driver: a negative sampling_frequency is now rejected instead
  of wrapping around (Sashiko).
- Base driver: channel .address is now an index into a per-channel
  register table instead of a raw register number, with a
  static_assert() against the channel enum; the calibration bias patch
  extends the same table instead of indexing by scan_index (Jonathan).
- Base driver: DEC_RATE is reset to 0 before a new value is written,
  as the datasheet recommends.
- FIFO: iio_for_each_active_channel() instead of reading the mask
  directly (Nuno, Jonathan, Sashiko); the scan buffer is sized with
  IIO_DECLARE_BUFFER_WITH_TS() and pushed with
  iio_push_to_buffers_with_ts() (Jonathan); the trigger handler uses
  the locking helper (Nuno).
- FIFO: the interrupt type is validated at probe and irq_flag is set
  to level-high, which was never set before (Sashiko); the FIFO is
  flushed and DIAG_STAT cleared when the buffer is disabled, so a
  stale session can't leak into the next (Sashiko).
- FIFO: fixed the FIFO_DATA transfer, whose header is 2 bytes and not
  4; samples were read 2 bytes late. Writing sampling_frequency while
  the buffer is enabled now returns -EBUSY.
- Sync: requested an exclusive clock rate instead of a notifier
  (Nuno), which also covers a controllable clock (Jonathan); the sync
  scale is now picked per requested sampling frequency (Nuno).
- Sync: corrected the accepted external clock ranges to the datasheet
  (direct 1 kHz to 8 kHz, scaled up to 400 Hz); the initial scale now
  keeps the multiplied clock at or below the internal rate; loss of
  sync or PLL lock is warned about from the trigger handler. Reverse
  xmas tree declarations and a loop-scoped unsigned counter
  (Jonathan).
- Link to v4: https://lore.kernel.org/r/20261023-adis16607-v4-0-a4d541ebfd13@analog.com

Changes in v4:
- Split the combined adislib commit into three: self_test callback,
  the reset-dispatch fix, and post_reset_setup callback, each its own
  patch - the original bundled all three under one "Also..." (Jonathan).
- Simplified the reset-dispatch fix: removed the internal settle-time
  sleep from __adis_reset() entirely and made both call sites
  (__adis_initial_startup(), adis_reset()) sleep unconditionally after
  dispatching through adis->ops->reset() - no more comparing against
  __adis_reset() to decide whether to sleep (Jonathan).
- dt-bindings: commit message now states how adis16607-2/-3 actually
  differ (gyro full-scale range only) instead of just asserting two
  variants exist (Jonathan). Narrowed reset-gpios's description to
  just itself, and expanded the top-level description to explain the
  device's five GPIO pins and where each one's function is actually
  modeled (clocks, interrupts, or not exposed) (Jonathan).
- Base driver: moved the RESET_GPIO_MASK force-write from after reset
  to before it, in probe() - a previously-defeated reset-pin routing
  (buggy driver, direct reg access) now gets a chance to be corrected
  before relying on the GPIO toggle, instead of too late to matter;
  only warns rather than erroring out on failure, so a still-live
  device keeps its chance at a real reset (Jonathan). Removed the
  now-redundant duplicate of that same write from
  adis16607_post_reset_setup() (Andy).
- Base driver: large style/cleanup batch - dropped an unneeded
  <linux/kernel.h> include, reordered struct adis16607 so struct adis
  is first (cheaper container_of(), measured -0.41% code size via
  bloat-o-meter), use spi_message_init_with_transfers() instead of
  repeated spi_message_add_tail(), ARRAY_SIZE() instead of hardcoded
  loop bounds, put_unaligned_be16(), combined the firmware/revision
  debugfs files into one "major.minor"-formatted entry, dropped an
  unnecessary #ifdef CONFIG_DEBUG_FS wrapper, guard()/
  adis_dev_auto_lock() instead of manual mutex lock/unlock, -ENODATA
  instead of -EINVAL for an unmatched chip_info, unit-suffixed
  clk_freq_mhz/int_clk_hz field names with MILLI from <linux/units.h>
  instead of a bare magic 1000, de-duplicated/inlined several
  single- or two-use macros (Andy, Jonathan).
- Base driver: the self-test startup delay now cites the actual
  datasheet value (Inertial Settling Time, 40ms) instead of an
  unexplained 100ms that didn't match anything in the datasheet
  (Andy). Self-test now clears the SELF_TEST register on every
  failure path, not just on success (Sashiko AI).
- FIFO: dropped disabling the FIFO before draining and the
  flush-and-re-enable afterward entirely - the datasheet confirms
  overflow-only overwrite (not overwrite-during-read) and a dual-port
  architecture with no read/write contention, so there was nothing
  for the disable step to protect against, and it was costing real
  samples during its own disabled window (Jonathan). fifo_buf is now
  a fixed-size array instead of a heap allocation reallocated on
  every scan-mode change; deleted the now-unnecessary
  update_scan_mode() and its devm cleanup entirely (Jonathan).
  guard()/adis_dev_auto_lock() and direct returns in buffer
  postenable/postdisable (Jonathan). clamp() instead of clamp_t() -
  confirmed by building that the forced cast wasn't needed (Jonathan).
- Sync: adis16607_config_clk() now reads DEC_RATE live instead of
  assuming it's 0 after reset, the same way adis16607_get_freq()
  already does - consistent with how USER_SYNC is already written
  explicitly rather than relying on its own reset default (Jonathan).
- Link to v3: https://lore.kernel.org/r/20261015-adis16607-v3-0-f972d4efee85@a

Changes in v3:
- Fixed the public adis_reset() wrapper (adis.h) to also dispatch
  through adis->ops->reset() instead of hardcoding __adis_reset() -
  the same bug class as the v2 __adis_initial_startup() fix, just a
  second call site that was missed. Switched both checks from
  comparing against adis_default_ops to adis->ops->reset != __adis_reset,
  which also works from the header, where adis_default_ops isn't
  visible (Sashiko).
- Fixed dt-binding wording: dropped "compatible" from the variant
  description (reads as a DT compatible-string fallback claim, which
  doesn't exist here - the two variants aren't interchangeable) and
  dropped a Linux-driver-specific sentence from reset-gpios's
  description (Conor); removed a stale commit-message bullet claiming
  a FIFO watermark property that was never in the binding (Sashiko).
- Dropped a redundant <linux/mod_devicetable.h> include -
  <linux/spi/spi.h> already provides it (Uwe, Andy); switched
  adis16607_id[] to named initializers (Uwe).
- Fixed FIFO sample timestamps: they were projected forward from the
  IRQ-fire time instead of back-dated, so the newest sample in a batch
  ended up timestamped in the future. Now back-dated so the newest
  sample lands on the IRQ-fire time and earlier ones count back from
  it (Sashiko).
- Added a comment explaining why disabling the FIFO during drain, and
  the flush afterward, don't lose in-flight samples - no new samples
  are enqueued at all while disabled, so nothing can arrive in that
  window for the flush to drop (Sashiko asked twice, independently).
- Reworked external clock sync configuration (adis16607_config_clk())
  so every mode (default/direct/scaled) explicitly writes its own
  complete SYNC_GPIO_MASK/USER_SYNC state, instead of only writing
  conditionally when enabling non-default sync (Sashiko x2); switched
  the sync scale factor from truncating to rounding division (Sashiko);
  fixed a real bug where SCALED mode's effective clock was hardcoded
  to the nominal internal rate instead of the actual achieved rate
  (ext_clk_hz * sync_scale), which would have caused FIFO timestamps
  to drift whenever the external clock didn't divide the internal
  clock exactly (Sashiko).
- Added range validation before writing calibration bias - out-of-
  range values were silently truncated to 16 bits instead of being
  rejected (Sashiko).
- Link to v2: https://lore.kernel.org/r/20261008-adis16607-v2-0-7c94df80878a@analog.com

Changes in v2:
- Split the driver commit into functional groupings per review:
  base, FIFO, external clock sync, calibration bias (Nuno).
- Added adis_data.self_test and adis_data.post_reset_setup callbacks
  to adislib instead of duplicating startup/self-test logic in the
  driver (Nuno). post_reset_setup is a second, honestly-named hook
  added after review feedback that overloading self_test to also mean
  "everything else the device needs at startup" was misleading.
- Fixed __adis_initial_startup()'s reset fallback to dispatch through
  adis->ops->reset() instead of a hardcoded __adis_reset() call -
  found while wiring up this driver's custom reset op. Added a Fixes
  tag for the originating commit.
- Removed the direct single-sample burst-read capture path entirely;
  the hardware FIFO is now the only buffered capture mode. Dropped
  adi,fifo-enable from the binding accordingly.
- Fixed FIFO-threshold interrupt wiring: set has_fifo on adis_data and
  switch the DT interrupt type from edge to level, matching the
  DEV_NOK signal this now uses (previously only the data-ready pulse
  was wired, which never actually generated a FIFO interrupt).
- Moved the optional reset-status GPIO output into the base driver
  patch (where it conceptually belongs) instead of bundling it into
  the FIFO patch's USER_GPIO_CFG write; the FIFO patch now updates
  that register with a read-modify-write instead of a blind write.
- Fixed cover letter: "Three compatible variants (-1, -2, -3)" was
  wrong, there are only two (-2, -3); "configurable data-ready
  interrupt pin (DIO1-4)" was wrong, there's a single fixed pin.
- Corrected spi-max-frequency to 10 MHz (datasheet maximum), from
  15 MHz.
- Renamed ADIS16607_REG_SW_RES to ADIS16607_REG_SOFT_RESET to match
  the datasheet's register name; renamed burst_buf to fifo_buf and
  dropped the redundant "fifo_" prefix from the buffer postenable/
  postdisable/ops names, since FIFO is the only capture mode and
  "burst" no longer described what that scratch buffer is used for.
- Link to v1: https://lore.kernel.org/r/20261001-adis16607-v1-0-9c89cee35d56@analog.com

---
Radu Sabau (8):
      iio: imu: adis: Fix custom reset dispatch in adislib
      iio: imu: adis: Add optional self_test callback
      iio: imu: adis: Add optional init callback
      dt-bindings: iio: imu: Add bindings for ADI ADIS16607
      iio: imu: Add driver for the ADI ADIS16607
      iio: imu: adis16607: Add FIFO-based buffered/triggered capture
      iio: imu: adis16607: Add external clock sync support
      iio: imu: adis16607: Add calibration bias support for gyro/accel

 .../devicetree/bindings/iio/imu/adi,adis16607.yaml |   94 ++
 MAINTAINERS                                        |    8 +
 drivers/iio/imu/Kconfig                            |   13 +
 drivers/iio/imu/Makefile                           |    1 +
 drivers/iio/imu/adis.c                             |   15 +-
 drivers/iio/imu/adis16607.c                        | 1496 ++++++++++++++++++++
 include/linux/iio/imu/adis.h                       |   26 +-
 7 files changed, 1648 insertions(+), 5 deletions(-)
---
base-commit: 0fcb05b72344f40806cbe810de8872f1698bf053
change-id: 20260617-adis16607-089ffc655ceb

Best regards,
-- 
Radu Sabau <radu.sabau@analog.com>



^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v5 1/8] iio: imu: adis: Fix custom reset dispatch in adislib
  2026-11-02  9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
@ 2026-11-02  9:25 ` Radu Sabau via B4 Relay
  2026-11-02  9:38   ` sashiko-bot
  2026-11-02  9:25 ` [PATCH v5 2/8] iio: imu: adis: Add optional self_test callback Radu Sabau via B4 Relay
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 13+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02  9:25 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron

From: Radu Sabau <radu.sabau@analog.com>

__adis_initial_startup()'s no-reset-gpio path called __adis_reset()
directly instead of adis->ops->reset(), silently ignoring any driver's
custom reset implementation. Invisible until now since
adis_default_ops.reset is __adis_reset() itself. Dispatch through
adis->ops->reset() instead - the same bug existed in the public
adis_reset() wrapper, fixed here too.

Move the settle-time sleep out of __adis_reset() and into both call
sites instead, so every reset op (default or custom) is treated
uniformly: adis->ops->reset() is expected to just do the write, and
the library always sleeps the settle time afterwards. This removes
the previous asymmetric "sleep here only for custom ops" special
case entirely.

Fixes: ecb010d44108 ("iio: imu: adis: Refactor adis_initial_startup")
Reviewed-by: Nuno Sá <nuno.sa@analog.com>
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
 drivers/iio/imu/adis.c       |  6 ++----
 include/linux/iio/imu/adis.h | 12 +++++++++++-
 2 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c
index a2bc1d14ed91..793e3b6b0ea7 100644
--- a/drivers/iio/imu/adis.c
+++ b/drivers/iio/imu/adis.c
@@ -340,7 +340,6 @@ EXPORT_SYMBOL_NS_GPL(__adis_check_status, "IIO_ADISLIB");
 int __adis_reset(struct adis *adis)
 {
 	int ret;
-	const struct adis_timeout *timeouts = adis->data->timeouts;
 
 	ret = __adis_write_reg_8(adis, adis->data->glob_cmd_reg,
 				 ADIS_GLOB_CMD_SW_RESET);
@@ -349,8 +348,6 @@ int __adis_reset(struct adis *adis)
 		return ret;
 	}
 
-	msleep(timeouts->sw_reset_ms);
-
 	return 0;
 }
 EXPORT_SYMBOL_NS_GPL(__adis_reset, "IIO_ADIS_LIB");
@@ -413,9 +410,10 @@ int __adis_initial_startup(struct adis *adis)
 		gpiod_set_value_cansleep(gpio, 0);
 		msleep(timeouts->reset_ms);
 	} else {
-		ret = __adis_reset(adis);
+		ret = adis->ops->reset(adis);
 		if (ret)
 			return ret;
+		msleep(timeouts->sw_reset_ms);
 	}
 
 	ret = adis_self_test(adis);
diff --git a/include/linux/iio/imu/adis.h b/include/linux/iio/imu/adis.h
index bfb6df68e6c9..97323b8d1cb3 100644
--- a/include/linux/iio/imu/adis.h
+++ b/include/linux/iio/imu/adis.h
@@ -10,6 +10,7 @@
 #define __IIO_ADIS_H__
 
 #include <linux/cleanup.h>
+#include <linux/delay.h>
 #include <linux/spi/spi.h>
 #include <linux/interrupt.h>
 #include <linux/iio/iio.h>
@@ -171,8 +172,17 @@ int __adis_reset(struct adis *adis);
  */
 static inline int adis_reset(struct adis *adis)
 {
+	int ret;
+
 	guard(mutex)(&adis->state_lock);
-	return __adis_reset(adis);
+
+	ret = adis->ops->reset(adis);
+	if (ret)
+		return ret;
+
+	msleep(adis->data->timeouts->sw_reset_ms);
+
+	return 0;
 }
 
 int __adis_write_reg(struct adis *adis, unsigned int reg,

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v5 2/8] iio: imu: adis: Add optional self_test callback
  2026-11-02  9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
  2026-11-02  9:25 ` [PATCH v5 1/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
@ 2026-11-02  9:25 ` Radu Sabau via B4 Relay
  2026-11-02  9:25 ` [PATCH v5 3/8] iio: imu: adis: Add optional init callback Radu Sabau via B4 Relay
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02  9:25 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron

From: Radu Sabau <radu.sabau@analog.com>

Add an optional self_test callback to struct adis_data, for devices
whose self-test doesn't fit the generic mask-write/sleep/check-status
sequence. Purely additive: defaults to NULL, no change for existing
drivers.

Reviewed-by: Nuno Sá <nuno.sa@analog.com>
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
 drivers/iio/imu/adis.c       | 3 +++
 include/linux/iio/imu/adis.h | 7 +++++++
 2 files changed, 10 insertions(+)

diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c
index 793e3b6b0ea7..5c3bb7259037 100644
--- a/drivers/iio/imu/adis.c
+++ b/drivers/iio/imu/adis.c
@@ -357,6 +357,9 @@ static int adis_self_test(struct adis *adis)
 	int ret;
 	const struct adis_timeout *timeouts = adis->data->timeouts;
 
+	if (adis->data->self_test)
+		return adis->data->self_test(adis);
+
 	ret = __adis_write_reg_16(adis, adis->data->self_test_reg,
 				  adis->data->self_test_mask);
 	if (ret) {
diff --git a/include/linux/iio/imu/adis.h b/include/linux/iio/imu/adis.h
index 97323b8d1cb3..c09b381eb619 100644
--- a/include/linux/iio/imu/adis.h
+++ b/include/linux/iio/imu/adis.h
@@ -52,6 +52,12 @@ struct adis_timeout {
  * @self_test_mask: Bitmask of supported self-test operations
  * @self_test_reg: Register address to request self test command
  * @self_test_no_autoclear: True if device's self-test needs clear of ctrl reg
+ * @self_test: Optional device-specific self-test override. When set,
+ *		it is called instead of the generic mask-write/sleep/
+ *		check-status sequence. The callback owns its own pass/fail
+ *		determination entirely (it should call __adis_check_status()
+ *		itself if it wants status-register checks). Must return 0 on
+ *		success, a negative error code otherwise.
  * @status_error_msgs: Array of error messages
  * @status_error_mask: Bitmask of errors supported by the device
  * @timeouts: Chip specific delays
@@ -82,6 +88,7 @@ struct adis_data {
 	unsigned int self_test_mask;
 	unsigned int self_test_reg;
 	bool self_test_no_autoclear;
+	int (*self_test)(struct adis *adis);
 	const struct adis_timeout *timeouts;
 
 	const char * const *status_error_msgs;

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v5 3/8] iio: imu: adis: Add optional init callback
  2026-11-02  9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
  2026-11-02  9:25 ` [PATCH v5 1/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
  2026-11-02  9:25 ` [PATCH v5 2/8] iio: imu: adis: Add optional self_test callback Radu Sabau via B4 Relay
@ 2026-11-02  9:25 ` Radu Sabau via B4 Relay
  2026-11-02  9:25 ` [PATCH v5 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607 Radu Sabau via B4 Relay
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02  9:25 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron

From: Radu Sabau <radu.sabau@analog.com>

Add an optional init callback to struct adis_data, called right
after reset and before self-test. Not all ADIS devices need extra
bring-up between reset and self-test (SPI mode locking, status or
bootloader checks, pin or burst-mode configuration, etc.) - purely
additive: defaults to NULL, no change for existing drivers.

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
 drivers/iio/imu/adis.c       | 6 ++++++
 include/linux/iio/imu/adis.h | 7 +++++++
 2 files changed, 13 insertions(+)

diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c
index 5c3bb7259037..e2cf42b8b946 100644
--- a/drivers/iio/imu/adis.c
+++ b/drivers/iio/imu/adis.c
@@ -419,6 +419,12 @@ int __adis_initial_startup(struct adis *adis)
 		msleep(timeouts->sw_reset_ms);
 	}
 
+	if (adis->data->init) {
+		ret = adis->data->init(adis);
+		if (ret)
+			return ret;
+	}
+
 	ret = adis_self_test(adis);
 	if (ret)
 		return ret;
diff --git a/include/linux/iio/imu/adis.h b/include/linux/iio/imu/adis.h
index c09b381eb619..c9016f636530 100644
--- a/include/linux/iio/imu/adis.h
+++ b/include/linux/iio/imu/adis.h
@@ -52,6 +52,12 @@ struct adis_timeout {
  * @self_test_mask: Bitmask of supported self-test operations
  * @self_test_reg: Register address to request self test command
  * @self_test_no_autoclear: True if device's self-test needs clear of ctrl reg
+ * @init: Optional device-specific hook called after reset,
+ *		before self-test. Not all ADIS devices need extra bring-up
+ *		between reset and self-test (SPI mode locking, status or
+ *		bootloader checks, pin or burst-mode configuration, etc.) -
+ *		only called when set. Must return 0 on success, a negative
+ *		error code otherwise.
  * @self_test: Optional device-specific self-test override. When set,
  *		it is called instead of the generic mask-write/sleep/
  *		check-status sequence. The callback owns its own pass/fail
@@ -88,6 +94,7 @@ struct adis_data {
 	unsigned int self_test_mask;
 	unsigned int self_test_reg;
 	bool self_test_no_autoclear;
+	int (*init)(struct adis *adis);
 	int (*self_test)(struct adis *adis);
 	const struct adis_timeout *timeouts;
 

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v5 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607
  2026-11-02  9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
                   ` (2 preceding siblings ...)
  2026-11-02  9:25 ` [PATCH v5 3/8] iio: imu: adis: Add optional init callback Radu Sabau via B4 Relay
@ 2026-11-02  9:25 ` Radu Sabau via B4 Relay
  2026-11-02 19:36   ` Conor Dooley
  2026-11-02  9:25 ` [PATCH v5 5/8] iio: imu: Add driver for the " Radu Sabau via B4 Relay
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 13+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02  9:25 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron

From: Radu Sabau <radu.sabau@analog.com>

Add device tree bindings for the Analog Devices ADIS16607 Precision
MEMS Inertial Measurement Unit. The ADIS16607 integrates a triaxial
gyroscope, triaxial accelerometer, and temperature sensor, and
communicates over SPI Half-Duplex.

Two variants are supported, adis16607-2 and adis16607-3, differing
only in gyroscope full-scale range (±450°/s and ±2000°/s
respectively).

The binding exposes optional properties for:
  - An external clock for sync mode (falling back to the internal 8 kHz
    clock when absent)
  - A hardware reset GPIO (active low)

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
 .../devicetree/bindings/iio/imu/adi,adis16607.yaml | 94 ++++++++++++++++++++++
 MAINTAINERS                                        |  7 ++
 2 files changed, 101 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml b/Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
new file mode 100644
index 000000000000..4545f942365c
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
@@ -0,0 +1,94 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/imu/adi,adis16607.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices ADIS16607 IMU
+
+maintainers:
+  - Radu Sabau <radu.sabau@analog.com>
+
+description: |
+  Analog Devices ADIS16607 Precision MEMS Inertial Measurement Unit (IMU)
+  with a triaxial gyroscope, triaxial accelerometer, and temperature sensor.
+  Uses SPI Half-Duplex communication protocol.
+
+  The device has five GPIO pins (GPIO0-GPIO4 in the datasheet), each
+  independently selectable for a single fixed function. This binding
+  exposes GPIO0's reset function as reset-gpios; GPIO2's sync function
+  is modeled via the clocks property, and GPIO4's DEV_NOK interrupt via
+  the interrupts property. GPIO3 (data-ready) is required internally
+  regardless of board wiring and isn't exposed. GPIO1 (wake) gates
+  whether the bootloader auto-loads its NVM contents after reset; it
+  defaults to enabled with a weak pull-up, which already gives normal
+  operation with no configuration needed, so it isn't exposed either.
+
+properties:
+  compatible:
+    enum:
+      - adi,adis16607-2
+      - adi,adis16607-3
+
+  reg:
+    maxItems: 1
+
+  spi-cpha: true
+
+  spi-cpol: true
+
+  spi-max-frequency:
+    maximum: 10000000
+
+  vdd-supply: true
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    description:
+      Optional external clock for sync mode. If not provided, the internal
+      8 kHz clock is used.
+    maxItems: 1
+
+  reset-gpios:
+    description:
+      Optional GPIO for hardware reset, active low. Routes to the
+      device's GPIO0 pin, which can be independently set to the reset
+      function or left idle via an on-chip register.
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - spi-cpha
+  - spi-cpol
+  - vdd-supply
+
+allOf:
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/gpio/gpio.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        imu@0 {
+            compatible = "adi,adis16607-2";
+            reg = <0>;
+            spi-cpha;
+            spi-cpol;
+            spi-max-frequency = <10000000>;
+            vdd-supply = <&vdd>;
+            interrupts = <4 IRQ_TYPE_LEVEL_HIGH>;
+            interrupt-parent = <&gpio>;
+            clocks = <&sync_clk>;
+            reset-gpios = <&gpio 10 GPIO_ACTIVE_LOW>;
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index f53047aa8d3c..d5679e8d0ffd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1792,6 +1792,13 @@ S:	Supported
 W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/devicetree/bindings/iio/imu/adi,adis16550.yaml
 
+ANALOG DEVICES INC ADIS16607 DRIVER
+M:	Radu Sabau <radu.sabau@analog.com>
+L:	linux-iio@vger.kernel.org
+S:	Supported
+W:	https://ez.analog.com/linux-software-drivers
+F:	Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
+
 ANALOG DEVICES INC ADL8113 DRIVER
 M:	Antoniu Miclaus <antoniu.miclaus@analog.com>
 L:	linux-iio@vger.kernel.org

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v5 5/8] iio: imu: Add driver for the ADI ADIS16607
  2026-11-02  9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
                   ` (3 preceding siblings ...)
  2026-11-02  9:25 ` [PATCH v5 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607 Radu Sabau via B4 Relay
@ 2026-11-02  9:25 ` Radu Sabau via B4 Relay
  2026-11-02  9:25 ` [PATCH v5 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture Radu Sabau via B4 Relay
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02  9:25 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron

From: Radu Sabau <radu.sabau@analog.com>

The ADIS16607 is a Precision MEMS IMU from Analog Devices with
triaxial gyroscope and accelerometer, delta angle/velocity, and
temperature channels, communicating over a custom SPI Half-Duplex
protocol. ADIS16607-2 has a ±450°/s gyroscope range, ADIS16607-3
±2000°/s; both share a ±40g accelerometer range and an 8 kHz internal
clock.

This is the base driver: probe, raw channel reads, sampling-frequency
control, startup/self-test via adislib, and debugfs identity entries.
No buffered capture, external clock sync, or calibration bias yet -
those follow in later patches.

Self-test leaves the device's SELF_TEST register cleared on every exit
path, not just on success, so a read failure or a failed delta check
doesn't leave the sensor stuck in its self-test/force state until the
next reset.

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
 MAINTAINERS                 |   1 +
 drivers/iio/imu/Kconfig     |  13 +
 drivers/iio/imu/Makefile    |   1 +
 drivers/iio/imu/adis16607.c | 894 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 909 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index d5679e8d0ffd..268a82adeb1d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1798,6 +1798,7 @@ L:	linux-iio@vger.kernel.org
 S:	Supported
 W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
+F:	drivers/iio/imu/adis16607.c
 
 ANALOG DEVICES INC ADL8113 DRIVER
 M:	Antoniu Miclaus <antoniu.miclaus@analog.com>
diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig
index 8bab4616be20..0b39057d0a7c 100644
--- a/drivers/iio/imu/Kconfig
+++ b/drivers/iio/imu/Kconfig
@@ -65,6 +65,19 @@ config ADIS16550
 	  To compile this driver as a module, choose M here: the module will be
 	  called adis16550.
 
+config ADIS16607
+	tristate "Analog Devices ADIS16607 and similar IMU driver"
+	depends on SPI
+	select IIO_ADIS_LIB
+	select IIO_ADIS_LIB_BUFFER if IIO_BUFFER
+	help
+	  Say yes here to build support for Analog Devices ADIS16607-2
+	  and ADIS16607-3 inertial sensors containing triaxis
+	  gyroscope and triaxis accelerometer.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called adis16607.
+
 source "drivers/iio/imu/bmi160/Kconfig"
 source "drivers/iio/imu/bmi270/Kconfig"
 source "drivers/iio/imu/bmi323/Kconfig"
diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile
index 3268dc2371ae..6eeec1ee387f 100644
--- a/drivers/iio/imu/Makefile
+++ b/drivers/iio/imu/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_ADIS16460) += adis16460.o
 obj-$(CONFIG_ADIS16475) += adis16475.o
 obj-$(CONFIG_ADIS16480) += adis16480.o
 obj-$(CONFIG_ADIS16550) += adis16550.o
+obj-$(CONFIG_ADIS16607) += adis16607.o
 
 adis_lib-y += adis.o
 adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_trigger.o
diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
new file mode 100644
index 000000000000..5e2c999e68ac
--- /dev/null
+++ b/drivers/iio/imu/adis16607.c
@@ -0,0 +1,894 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ADIS16607 IMU driver
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/build_bug.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/imu/adis.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/seq_file.h>
+#include <linux/spi/spi.h>
+#include <linux/unaligned.h>
+#include <linux/units.h>
+
+/* Register addresses */
+#define ADIS16607_REG_DEV_ID		0x00
+#define ADIS16607_REG_REV_ID		0x01
+#define ADIS16607_REG_SERIAL_NUM0	0x02
+#define ADIS16607_REG_SERIAL_NUM1	0x03
+#define ADIS16607_REG_SERIAL_NUM2	0x04
+#define ADIS16607_REG_DIAG_STAT		0x05
+#define ADIS16607_REG_X_ACCEL		0x06
+#define ADIS16607_REG_Y_ACCEL		0x08
+#define ADIS16607_REG_Z_ACCEL		0x0A
+#define ADIS16607_REG_X_GYRO		0x0C
+#define ADIS16607_REG_Y_GYRO		0x0E
+#define ADIS16607_REG_Z_GYRO		0x10
+#define ADIS16607_REG_X_DELTVEL		0x12
+#define ADIS16607_REG_Y_DELTVEL		0x14
+#define ADIS16607_REG_Z_DELTVEL		0x16
+#define ADIS16607_REG_X_DELTANG		0x18
+#define ADIS16607_REG_Y_DELTANG		0x1A
+#define ADIS16607_REG_Z_DELTANG		0x1C
+#define ADIS16607_REG_TEMP		0x20
+#define ADIS16607_REG_USER_GPIO_CFG	0x2F
+#define ADIS16607_REG_SPI_HALFDUPLEX	0x32
+#define ADIS16607_REG_SOFT_RESET	0x36
+#define ADIS16607_REG_SELF_TEST		0x39
+#define ADIS16607_REG_DEC_RATE		0x3A
+#define ADIS16607_REG_DIGITAL_STATUS	0x4E
+
+/* SPI Half-Duplex lock value */
+#define ADIS16607_LOCK_SPI_HALFDUPLEX	0xB4B4
+
+/* Masks */
+#define ADIS16607_RESET_GPIO_MASK	GENMASK(2, 0)
+#define ADIS16607_BOOTLOADER_BUSY_MASK	BIT(0)
+
+/* Self-test registers and masks */
+#define ADIS16607_REG_SELF_TEST_DATA(x)	((x) + 0x23)
+#define ADIS16607_SNSR_SELF_TEST_MASK	BIT(6)
+#define ADIS16607_ST_FORCE_MASK		BIT(7)
+
+/* Self-test delta check limits */
+#define ADIS16607_ACCEL_XY_DELTA_MAX	260
+#define ADIS16607_ACCEL_Z_DELTA_MAX	4000
+#define ADIS16607_GYRO_DELTA_MAX	2600
+
+struct adis16607_chip_info {
+	const struct iio_chan_spec *channels;
+	const char *name;
+	u32 num_channels;
+	u32 gyro_max_val;
+	u32 gyro_max_scale;
+	u32 accel_max_val;
+	u32 accel_max_scale;
+	u32 temp_scale;
+	u32 deltang_max_val;
+	u32 deltvel_max_val;
+	u32 int_clk_Hz;
+	u16 max_dec;
+};
+
+struct adis16607 {
+	struct adis adis;
+	const struct adis16607_chip_info *info;
+	unsigned long clk_freq_mHz;
+	u64 sample_period_ns;
+};
+
+enum {
+	ADIS16607_SCAN_ACCEL_X,
+	ADIS16607_SCAN_ACCEL_Y,
+	ADIS16607_SCAN_ACCEL_Z,
+	ADIS16607_SCAN_GYRO_X,
+	ADIS16607_SCAN_GYRO_Y,
+	ADIS16607_SCAN_GYRO_Z,
+	ADIS16607_SCAN_DELTVEL_X,
+	ADIS16607_SCAN_DELTVEL_Y,
+	ADIS16607_SCAN_DELTVEL_Z,
+	ADIS16607_SCAN_DELTANG_X,
+	ADIS16607_SCAN_DELTANG_Y,
+	ADIS16607_SCAN_DELTANG_Z,
+	ADIS16607_SCAN_TEMP,
+	ADIS16607_SCAN_MAX,
+};
+
+struct adis16607_chan_regs {
+	u16 raw_reg;
+};
+
+static const struct adis16607_chan_regs adis16607_chan_regs[] = {
+	[ADIS16607_SCAN_ACCEL_X]   = { ADIS16607_REG_X_ACCEL },
+	[ADIS16607_SCAN_ACCEL_Y]   = { ADIS16607_REG_Y_ACCEL },
+	[ADIS16607_SCAN_ACCEL_Z]   = { ADIS16607_REG_Z_ACCEL },
+	[ADIS16607_SCAN_GYRO_X]    = { ADIS16607_REG_X_GYRO },
+	[ADIS16607_SCAN_GYRO_Y]    = { ADIS16607_REG_Y_GYRO },
+	[ADIS16607_SCAN_GYRO_Z]    = { ADIS16607_REG_Z_GYRO },
+	[ADIS16607_SCAN_DELTVEL_X] = { ADIS16607_REG_X_DELTVEL },
+	[ADIS16607_SCAN_DELTVEL_Y] = { ADIS16607_REG_Y_DELTVEL },
+	[ADIS16607_SCAN_DELTVEL_Z] = { ADIS16607_REG_Z_DELTVEL },
+	[ADIS16607_SCAN_DELTANG_X] = { ADIS16607_REG_X_DELTANG },
+	[ADIS16607_SCAN_DELTANG_Y] = { ADIS16607_REG_Y_DELTANG },
+	[ADIS16607_SCAN_DELTANG_Z] = { ADIS16607_REG_Z_DELTANG },
+	[ADIS16607_SCAN_TEMP]      = { ADIS16607_REG_TEMP },
+};
+
+static_assert(ARRAY_SIZE(adis16607_chan_regs) == ADIS16607_SCAN_MAX);
+
+static int adis16607_spi_read(struct adis *adis, unsigned int reg,
+			      unsigned int *val, unsigned int size)
+{
+	struct spi_message msg;
+	struct spi_transfer xfers[2] = {
+		{
+			.tx_buf = adis->tx,
+			.len = 4,
+			.cs_change = 1,
+		},
+		{
+			.tx_buf = adis->tx,
+			.rx_buf = adis->rx,
+			.len = 4,
+		},
+	};
+	u16 val_msw, val_lsw;
+	int ret;
+
+	/*
+	 * Half-Duplex read: two 4-byte transfers with CS toggling in between.
+	 * The first one latches the command, the second sends it again and
+	 * the device answers in bytes 2-3.
+	 */
+	switch (size) {
+	case 2:
+		adis->tx[0] = reg | BIT(7);
+
+		spi_message_init_with_transfers(&msg, xfers, ARRAY_SIZE(xfers));
+
+		ret = spi_sync(adis->spi, &msg);
+		if (ret)
+			return ret;
+
+		*val = get_unaligned_be16(&adis->rx[2]);
+		return 0;
+
+	case 4:
+		/* Read MSW from reg */
+		adis->tx[0] = reg | BIT(7);
+
+		spi_message_init_with_transfers(&msg, xfers, ARRAY_SIZE(xfers));
+
+		ret = spi_sync(adis->spi, &msg);
+		if (ret)
+			return ret;
+
+		val_msw = get_unaligned_be16(&adis->rx[2]);
+
+		/* Read LSW from reg+1 */
+		adis->tx[0] = (reg + 1) | BIT(7);
+
+		spi_message_init_with_transfers(&msg, xfers, ARRAY_SIZE(xfers));
+
+		ret = spi_sync(adis->spi, &msg);
+		if (ret)
+			return ret;
+
+		val_lsw = adis->rx[2];
+
+		/*
+		 * Combine: 24-bit value with MSW in upper 16 bits,
+		 * LSW in lower 8 bits.
+		 */
+		*val = FIELD_PREP(GENMASK(23, 8), val_msw) | val_lsw;
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+/*
+ * SPI Half-Duplex write operation for ADIS16607.
+ * Protocol:
+ *   - Send 3 bytes: [reg, val_high, val_low]
+ *   - Only 16-bit writes are supported
+ */
+static int adis16607_spi_write(struct adis *adis, unsigned int reg,
+			       unsigned int val, unsigned int size)
+{
+	if (size != 2)
+		return -EINVAL;
+
+	adis->tx[0] = reg;
+	put_unaligned_be16(val, &adis->tx[1]);
+
+	return spi_write(adis->spi, adis->tx, 3);
+}
+
+static int adis16607_reset(struct adis *adis)
+{
+	return adis16607_spi_write(adis, ADIS16607_REG_SOFT_RESET, BIT(0), 2);
+}
+
+static const struct adis_ops adis16607_ops = {
+	.read = adis16607_spi_read,
+	.write = adis16607_spi_write,
+	.reset = adis16607_reset,
+};
+
+/*
+ * Check if the delta between two values is within specified range.
+ */
+static bool adis16607_delta_check(s16 val1, s16 val2, s16 delta_max)
+{
+	return abs(val1 - val2) <= delta_max;
+}
+
+/*
+ * Perform sensor self-test for ADIS16607.
+ * Reads self-test data before and after forcing self-test,
+ * then verifies delta values are within acceptable ranges.
+ */
+static int adis16607_sensor_self_test(struct adis16607 *st)
+{
+	struct adis *adis = &st->adis;
+	u16 st1_val[6], st2_val[6];
+	s16 delta_max[6] = {
+		ADIS16607_ACCEL_XY_DELTA_MAX,
+		ADIS16607_ACCEL_XY_DELTA_MAX,
+		ADIS16607_ACCEL_Z_DELTA_MAX,
+		ADIS16607_GYRO_DELTA_MAX,
+		ADIS16607_GYRO_DELTA_MAX,
+		ADIS16607_GYRO_DELTA_MAX,
+	};
+	int ret;
+
+	/* Enable sensor self-test */
+	ret = __adis_write_reg_16(adis, ADIS16607_REG_SELF_TEST,
+				  ADIS16607_SNSR_SELF_TEST_MASK);
+	if (ret)
+		return ret;
+
+	/* Wait for self-test to settle before reading results */
+	msleep(adis->data->timeouts->self_test_ms);
+
+	/* Read first set of self-test data */
+	for (unsigned int i = 0; i < ARRAY_SIZE(st1_val); i++) {
+		ret = __adis_read_reg_16(adis, ADIS16607_REG_SELF_TEST_DATA(i),
+					 &st1_val[i]);
+		if (ret)
+			goto clear_self_test;
+	}
+
+	/* Force self-test */
+	ret = __adis_write_reg_16(adis, ADIS16607_REG_SELF_TEST,
+				  ADIS16607_SNSR_SELF_TEST_MASK | ADIS16607_ST_FORCE_MASK);
+	if (ret)
+		goto clear_self_test;
+
+	/* Wait for forced self-test to settle before reading results */
+	msleep(adis->data->timeouts->self_test_ms);
+
+	/* Read second set of self-test data */
+	for (unsigned int i = 0; i < ARRAY_SIZE(st2_val); i++) {
+		ret = __adis_read_reg_16(adis, ADIS16607_REG_SELF_TEST_DATA(i),
+					 &st2_val[i]);
+		if (ret)
+			goto clear_self_test;
+
+		if (!adis16607_delta_check((s16)st1_val[i], (s16)st2_val[i],
+					   delta_max[i])) {
+			ret = dev_err_probe(&adis->spi->dev, -EINVAL,
+					    "Self-test failed for axis %u: delta out of range\n",
+					    i);
+			goto clear_self_test;
+		}
+	}
+
+	/* Clear self-test register */
+	return __adis_write_reg_16(adis, ADIS16607_REG_SELF_TEST, 0);
+
+clear_self_test:
+	/*
+	 * Best-effort cleanup after a failure above - leave the device out
+	 * of its self-test/force state rather than stuck in it until the
+	 * next reset. Warn rather than returning this write's own result,
+	 * so a cleanup failure doesn't mask the real error that got us here.
+	 */
+	if (__adis_write_reg_16(adis, ADIS16607_REG_SELF_TEST, 0))
+		dev_warn(&adis->spi->dev, "failed to clear self-test register\n");
+
+	return ret;
+}
+
+/*
+ * Called by __adis_initial_startup() right after reset, before self-test:
+ * SPI Half-Duplex locking, dev_id verification and the bootloader check.
+ */
+static int adis16607_init(struct adis *adis)
+{
+	struct device *dev = &adis->spi->dev;
+	u16 diag_stat, digital_status, dev_id;
+	int ret;
+
+	/* Lock the device into SPI Half-Duplex mode */
+	ret = __adis_write_reg_16(adis, ADIS16607_REG_SPI_HALFDUPLEX,
+				  ADIS16607_LOCK_SPI_HALFDUPLEX);
+	if (ret)
+		return ret;
+
+	/* Verify the correct device is attached */
+	ret = __adis_read_reg_16(adis, ADIS16607_REG_DEV_ID, &dev_id);
+	if (ret)
+		return ret;
+
+	if (dev_id != adis->data->prod_id)
+		return dev_err_probe(dev, -ENODEV,
+				     "device ID mismatch: expected 0x%04x, got 0x%04x\n",
+				     adis->data->prod_id, dev_id);
+
+	/* Read diag_stat to clear any latched faults */
+	ret = __adis_read_reg_16(adis, ADIS16607_REG_DIAG_STAT, &diag_stat);
+	if (ret)
+		return ret;
+
+	ret = __adis_check_status(adis);
+	if (ret)
+		return ret;
+
+	/* Check that bootloader is not busy */
+	ret = __adis_read_reg_16(adis, ADIS16607_REG_DIGITAL_STATUS, &digital_status);
+	if (ret)
+		return ret;
+
+	if (digital_status & ADIS16607_BOOTLOADER_BUSY_MASK)
+		return dev_err_probe(dev, -EBUSY, "bootloader is busy\n");
+
+	return 0;
+}
+
+/*
+ * Self-test callback for struct adis_data (see adis16607_data below).
+ * Called by __adis_initial_startup() right after adis16607_init().
+ */
+static int adis16607_self_test(struct adis *adis)
+{
+	struct adis16607 *st = container_of(adis, struct adis16607, adis);
+	u16 diag_stat;
+	int ret;
+
+	/*
+	 * Inertial Settling Time (Table 1), needed before self-test
+	 * reads sensor data.
+	 */
+	msleep(40);
+
+	ret = adis16607_sensor_self_test(st);
+	if (ret)
+		return ret;
+
+	/* Read diag_stat again to clear any faults latched during self-test */
+	ret = __adis_read_reg_16(adis, ADIS16607_REG_DIAG_STAT, &diag_stat);
+	if (ret)
+		return ret;
+
+	return __adis_check_status(adis);
+}
+
+static int adis16607_firmware_revision_show(struct seq_file *s, void *arg)
+{
+	struct adis16607 *st = s->private;
+	struct {
+		u8 minor;
+		u8 major;
+	} rev;
+	u16 tmp;
+	int ret;
+
+	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_REV_ID, &tmp);
+	if (ret)
+		return ret;
+
+	rev.major = FIELD_GET(GENMASK(15, 8), tmp);
+	rev.minor = FIELD_GET(GENMASK(7, 0), tmp);
+
+	seq_printf(s, "%u.%u\n", rev.major, rev.minor);
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(adis16607_firmware_revision);
+
+static int adis16607_show_serial_number(void *arg, u64 *val)
+{
+	struct adis16607 *st = arg;
+	u64 serial;
+	u16 tmp;
+	int ret;
+
+	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM2, &tmp);
+	if (ret)
+		return ret;
+
+	serial = tmp;
+
+	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM1, &tmp);
+	if (ret)
+		return ret;
+
+	serial <<= 16;
+	serial |= tmp;
+
+	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM0, &tmp);
+	if (ret)
+		return ret;
+
+	serial <<= 16;
+	serial |= tmp;
+
+	*val = serial;
+	return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(adis16607_serial_number_fops,
+			 adis16607_show_serial_number, NULL, "0x%.8llx\n");
+
+static int adis16607_show_dev_id(void *arg, u64 *val)
+{
+	struct adis16607 *st = arg;
+	u16 dev_id;
+	int ret;
+
+	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_DEV_ID, &dev_id);
+	if (ret)
+		return ret;
+
+	*val = dev_id;
+	return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(adis16607_dev_id_fops,
+			 adis16607_show_dev_id, NULL, "%llu\n");
+
+static void adis16607_debugfs_init(struct iio_dev *indio_dev)
+{
+	struct adis16607 *st = iio_priv(indio_dev);
+	struct dentry *d = iio_get_debugfs_dentry(indio_dev);
+
+	debugfs_create_file_unsafe("serial_number", 0400, d, st,
+				   &adis16607_serial_number_fops);
+	debugfs_create_file_unsafe("dev_id", 0400, d, st,
+				   &adis16607_dev_id_fops);
+	debugfs_create_file("firmware_revision", 0400, d, st,
+			    &adis16607_firmware_revision_fops);
+}
+
+static int adis16607_get_freq(struct adis16607 *st, u32 *freq)
+{
+	u16 dec;
+	int ret;
+
+	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, &dec);
+	if (ret)
+		return ret;
+
+	*freq = DIV_ROUND_CLOSEST(st->clk_freq_mHz, dec + 1);
+
+	return 0;
+}
+
+static int adis16607_set_freq(struct adis16607 *st, u32 freq)
+{
+	u64 period_ns;
+	u32 dec;
+	int ret;
+
+	if (!freq)
+		return -EINVAL;
+
+	dec = DIV_ROUND_CLOSEST(st->clk_freq_mHz, freq);
+	if (dec)
+		dec--;
+
+	dec = min(dec, st->info->max_dec);
+
+	/*
+	 * The sample period is (dec + 1) / clk_freq. clk_freq_mHz is in
+	 * millihertz, hence the MILLIHZ_PER_HZ factor to get back to ns.
+	 * Starting from a u64 forces every multiplication below to 64 bits:
+	 * NSEC_PER_SEC * MILLIHZ_PER_HZ alone (~1e12) overflows 32 bits.
+	 */
+	period_ns = NSEC_PER_SEC;
+	period_ns *= MILLIHZ_PER_HZ;
+	period_ns *= dec + 1;
+	period_ns = DIV_ROUND_CLOSEST_ULL(period_ns, st->clk_freq_mHz);
+
+	adis_dev_auto_scoped_lock(&st->adis) {
+		/* Reset the decimation accumulator before the new value */
+		ret = __adis_write_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, 0);
+		if (ret)
+			return ret;
+
+		if (dec) {
+			ret = __adis_write_reg_16(&st->adis,
+						  ADIS16607_REG_DEC_RATE, dec);
+			if (ret)
+				return ret;
+		}
+
+		st->sample_period_ns = period_ns;
+	}
+
+	return 0;
+}
+
+/*
+ * .address is an index into adis16607_chan_regs[] for this driver, not a
+ * literal register like adis_single_conversion() expects - resolve it on a
+ * local copy rather than reimplementing the helper's locking and
+ * sign-extension logic ourselves.
+ */
+static int adis16607_raw_read(struct iio_dev *indio_dev,
+			      const struct iio_chan_spec *chan, int *val)
+{
+	struct iio_chan_spec raw_chan = *chan;
+
+	raw_chan.address = adis16607_chan_regs[chan->address].raw_reg;
+
+	return adis_single_conversion(indio_dev, &raw_chan, 0, val);
+}
+
+static int adis16607_read_raw(struct iio_dev *indio_dev,
+			      const struct iio_chan_spec *chan,
+			      int *val, int *val2, long info)
+{
+	struct adis16607 *st = iio_priv(indio_dev);
+	int ret;
+	u32 tmp;
+
+	switch (info) {
+	case IIO_CHAN_INFO_RAW:
+		return adis16607_raw_read(indio_dev, chan, val);
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->type) {
+		case IIO_ANGL_VEL:
+			*val = st->info->gyro_max_val;
+			*val2 = st->info->gyro_max_scale;
+			return IIO_VAL_FRACTIONAL;
+		case IIO_ACCEL:
+			*val = st->info->accel_max_val;
+			*val2 = st->info->accel_max_scale;
+			return IIO_VAL_FRACTIONAL;
+		case IIO_TEMP:
+			*val = st->info->temp_scale;
+			return IIO_VAL_INT;
+		case IIO_DELTA_ANGL:
+			*val = st->info->deltang_max_val;
+			*val2 = 23;
+			return IIO_VAL_FRACTIONAL_LOG2;
+		case IIO_DELTA_VELOCITY:
+			*val = st->info->deltvel_max_val;
+			*val2 = 23;
+			return IIO_VAL_FRACTIONAL_LOG2;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_OFFSET:
+		/* Temperature: 0 raw counts = 25°C; offset = 25000 mdeg / 5 mdeg per LSB = 5000 */
+		*val = 5000;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		ret = adis16607_get_freq(st, &tmp);
+		if (ret)
+			return ret;
+		/* tmp is in mHz: split into Hz and the remainder in uHz */
+		*val = tmp / MILLIHZ_PER_HZ;
+		*val2 = (tmp % MILLIHZ_PER_HZ) *
+			(MICROHZ_PER_HZ / MILLIHZ_PER_HZ);
+		return IIO_VAL_INT_PLUS_MICRO;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int adis16607_write_raw(struct iio_dev *indio_dev,
+			       const struct iio_chan_spec *chan,
+			       int val, int val2, long info)
+{
+	struct adis16607 *st = iio_priv(indio_dev);
+	u32 tmp;
+
+	switch (info) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		if (val < 0 || val2 < 0)
+			return -EINVAL;
+
+		/* val is in Hz and val2 in uHz: convert both to mHz */
+		tmp = val * MILLIHZ_PER_HZ +
+		      val2 / (MICROHZ_PER_HZ / MILLIHZ_PER_HZ);
+		return adis16607_set_freq(st, tmp);
+
+	default:
+		return -EINVAL;
+	}
+}
+
+/*
+ * .address indexes adis16607_chan_regs[], .scan_index is the buffer position;
+ * both come from the same enum today but are separate fields on purpose.
+ */
+#define ADIS16607_MOD_CHAN(_type, _mod, _idx)				       \
+	{								       \
+		.type = (_type),					       \
+		.modified = 1,						       \
+		.channel2 = (_mod),					       \
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		       \
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),	       \
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),       \
+		.address = (_idx),					       \
+		.scan_index = (_idx),					       \
+		.scan_type = {						       \
+			.sign = 's',					       \
+			.realbits = 24,					       \
+			.storagebits = 32,				       \
+			.endianness = IIO_BE,				       \
+		},							       \
+	}
+
+#define ADIS16607_ACCEL_CHANNEL(_mod)					       \
+	ADIS16607_MOD_CHAN(IIO_ACCEL, IIO_MOD_ ## _mod,			       \
+			   ADIS16607_SCAN_ACCEL_ ## _mod)
+
+#define ADIS16607_GYRO_CHANNEL(_mod)					       \
+	ADIS16607_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_ ## _mod,		       \
+			   ADIS16607_SCAN_GYRO_ ## _mod)
+
+#define ADIS16607_MOD_CHAN_DELTA(_type, _mod, _idx)			       \
+	{								       \
+		.type = (_type),					       \
+		.modified = 1,						       \
+		.channel2 = (_mod),					       \
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		       \
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),	       \
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),       \
+		.address = (_idx),					       \
+		.scan_index = (_idx),					       \
+		.scan_type = {						       \
+			.sign = 's',					       \
+			.realbits = 24,					       \
+			.storagebits = 32,				       \
+			.endianness = IIO_BE,				       \
+		},							       \
+	}
+
+#define ADIS16607_DELTANG_CHAN(_mod)					       \
+	ADIS16607_MOD_CHAN_DELTA(IIO_DELTA_ANGL, IIO_MOD_ ## _mod,	       \
+				 ADIS16607_SCAN_DELTANG_ ## _mod)
+
+#define ADIS16607_DELTVEL_CHAN(_mod)					       \
+	ADIS16607_MOD_CHAN_DELTA(IIO_DELTA_VELOCITY, IIO_MOD_ ## _mod,	       \
+				 ADIS16607_SCAN_DELTVEL_ ## _mod)
+
+static const struct iio_chan_spec adis16607_channels[] = {
+	ADIS16607_ACCEL_CHANNEL(X),
+	ADIS16607_ACCEL_CHANNEL(Y),
+	ADIS16607_ACCEL_CHANNEL(Z),
+	ADIS16607_GYRO_CHANNEL(X),
+	ADIS16607_GYRO_CHANNEL(Y),
+	ADIS16607_GYRO_CHANNEL(Z),
+	ADIS16607_DELTVEL_CHAN(X),
+	ADIS16607_DELTVEL_CHAN(Y),
+	ADIS16607_DELTVEL_CHAN(Z),
+	ADIS16607_DELTANG_CHAN(X),
+	ADIS16607_DELTANG_CHAN(Y),
+	ADIS16607_DELTANG_CHAN(Z),
+	{
+		.type = IIO_TEMP,
+		.indexed = 1,
+		.channel = 0,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE) |
+				      BIT(IIO_CHAN_INFO_OFFSET),
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+		.address = ADIS16607_SCAN_TEMP,
+		.scan_index = ADIS16607_SCAN_TEMP,
+		.scan_type = {
+			.sign = 's',
+			.realbits = 16,
+			.storagebits = 16,
+			.endianness = IIO_BE,
+		},
+	},
+	IIO_CHAN_SOFT_TIMESTAMP(ADIS16607_SCAN_MAX),
+};
+
+static const struct iio_info adis16607_info = {
+	.read_raw = adis16607_read_raw,
+	.write_raw = adis16607_write_raw,
+	.debugfs_reg_access = adis_debugfs_reg_access,
+};
+
+static const char * const adis16607_status_error_msgs[] = {
+	[9] = "Boot Memory Failure",
+	[11] = "Power Supply Failure",
+	[12] = "Accelerometer Failure",
+	[13] = "Gyroscope Failure",
+};
+
+static const struct adis_timeout adis16607_timeouts = {
+	.reset_ms = 130,
+	.sw_reset_ms = 50,
+	.self_test_ms = 15,
+};
+
+static const struct adis_data adis16607_data = {
+	.diag_stat_reg = ADIS16607_REG_DIAG_STAT,
+	.diag_stat_size = 2,
+	.prod_id_reg = ADIS16607_REG_DEV_ID,
+	.prod_id = 0x6000,
+	.unmasked_drdy = true,
+	.init = adis16607_init,
+	.self_test = adis16607_self_test,
+	.status_error_msgs = adis16607_status_error_msgs,
+	.status_error_mask = BIT(9) | BIT(11) | BIT(12) | BIT(13),
+	.timeouts = &adis16607_timeouts,
+};
+
+/*
+ * ADIS16607-2: 450 deg/s gyro, 40g accel
+ * ADIS16607-3: 2000 deg/s gyro, 40g accel
+ */
+static const struct adis16607_chip_info adis16607_2_chip_info = {
+	.channels	= adis16607_channels,
+	.num_channels	= ARRAY_SIZE(adis16607_channels),
+	.name		= "adis16607-2",
+	.gyro_max_val	= IIO_DEGREE_TO_RAD(450),
+	.gyro_max_scale	= 30000 << 8,
+	.accel_max_val	= IIO_G_TO_M_S_2(40),
+	.accel_max_scale = 31250 << 8,
+	.temp_scale	= 5,
+	.deltang_max_val = IIO_DEGREE_TO_RAD(450),
+	.deltvel_max_val = IIO_G_TO_M_S_2(40),
+	.int_clk_Hz	= 8000,
+	.max_dec	= 65535,
+};
+
+static const struct adis16607_chip_info adis16607_3_chip_info = {
+	.channels	= adis16607_channels,
+	.num_channels	= ARRAY_SIZE(adis16607_channels),
+	.name		= "adis16607-3",
+	.gyro_max_val	= IIO_DEGREE_TO_RAD(2000),
+	.gyro_max_scale	= 31250 << 8,
+	.accel_max_val	= IIO_G_TO_M_S_2(40),
+	.accel_max_scale = 31250 << 8,
+	.temp_scale	= 5,
+	.deltang_max_val = IIO_DEGREE_TO_RAD(2000),
+	.deltvel_max_val = IIO_G_TO_M_S_2(40),
+	.int_clk_Hz	= 8000,
+	.max_dec	= 65535,
+};
+
+/*
+ * No external clock sync support yet: always run on the internal clock.
+ * Default dec=0 after reset, so period = 1 / clk_freq_mHz.
+ */
+static int adis16607_config_clk(struct adis16607 *st)
+{
+	u64 period_ns = NSEC_PER_SEC;
+
+	st->clk_freq_mHz = st->info->int_clk_Hz * MILLIHZ_PER_HZ;
+
+	/* clk_freq_mHz is in millihertz: scale to get the period in ns */
+	period_ns *= MILLIHZ_PER_HZ;
+	st->sample_period_ns = DIV_ROUND_CLOSEST_ULL(period_ns,
+						     st->clk_freq_mHz);
+	return 0;
+}
+
+static int adis16607_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct iio_dev *indio_dev;
+	struct adis16607 *st;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	st = iio_priv(indio_dev);
+	st->info = spi_get_device_match_data(spi);
+	if (!st->info)
+		return -ENODATA;
+
+	indio_dev->name = st->info->name;
+	indio_dev->channels = st->info->channels;
+	indio_dev->num_channels = st->info->num_channels;
+	indio_dev->info = &adis16607_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	st->adis.ops = &adis16607_ops;
+
+	ret = devm_regulator_get_enable(dev, "vdd");
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get vdd regulator\n");
+
+	ret = adis_init(&st->adis, indio_dev, spi, &adis16607_data);
+	if (ret)
+		return ret;
+
+	/*
+	 * Force GPIO0 back to its RESET function before relying on toggling
+	 * it below - RESET_GPIO_MASK is runtime-writable on this part, so a
+	 * prior write (buggy driver, direct reg access, etc.) could have
+	 * left it de-routed, which would make the GPIO toggle in
+	 * __adis_initial_startup() silently do nothing. This write goes out
+	 * over SPI, which stays live regardless of this mask's state, so it
+	 * can re-arm the mux as long as the device is merely
+	 * reset-pin-defeated and not fully unresponsive. Only warn on
+	 * failure instead of erroring out - bailing here would throw away
+	 * whatever chance the GPIO toggle below still has of working.
+	 */
+	if (device_property_present(dev, "reset-gpios")) {
+		ret = __adis_update_bits_base(&st->adis, ADIS16607_REG_USER_GPIO_CFG,
+					      ADIS16607_RESET_GPIO_MASK,
+					      FIELD_PREP(ADIS16607_RESET_GPIO_MASK, 1), 2);
+		if (ret)
+			dev_warn(dev, "failed to force reset-pin routing: %d\n", ret);
+	}
+
+	ret = __adis_initial_startup(&st->adis);
+	if (ret)
+		return ret;
+
+	ret = adis16607_config_clk(st);
+	if (ret)
+		return ret;
+
+	ret = devm_iio_device_register(dev, indio_dev);
+	if (ret)
+		return ret;
+
+	adis16607_debugfs_init(indio_dev);
+
+	return 0;
+}
+
+static const struct spi_device_id adis16607_id[] = {
+	{ .name = "adis16607-2", .driver_data = (kernel_ulong_t)&adis16607_2_chip_info },
+	{ .name = "adis16607-3", .driver_data = (kernel_ulong_t)&adis16607_3_chip_info },
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, adis16607_id);
+
+static const struct of_device_id adis16607_of_match[] = {
+	{ .compatible = "adi,adis16607-2", .data = &adis16607_2_chip_info },
+	{ .compatible = "adi,adis16607-3", .data = &adis16607_3_chip_info },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, adis16607_of_match);
+
+static struct spi_driver adis16607_driver = {
+	.driver = {
+		.name = "adis16607",
+		.of_match_table = adis16607_of_match,
+	},
+	.probe = adis16607_probe,
+	.id_table = adis16607_id,
+};
+module_spi_driver(adis16607_driver);
+
+MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADIS16607 and similar IMU driver");
+MODULE_IMPORT_NS("IIO_ADISLIB");
+MODULE_LICENSE("GPL");

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v5 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture
  2026-11-02  9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
                   ` (4 preceding siblings ...)
  2026-11-02  9:25 ` [PATCH v5 5/8] iio: imu: Add driver for the " Radu Sabau via B4 Relay
@ 2026-11-02  9:25 ` Radu Sabau via B4 Relay
  2026-11-02  9:47   ` sashiko-bot
  2026-11-02  9:25 ` [PATCH v5 7/8] iio: imu: adis16607: Add external clock sync support Radu Sabau via B4 Relay
  2026-11-02  9:25 ` [PATCH v5 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel Radu Sabau via B4 Relay
  7 siblings, 1 reply; 13+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02  9:25 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron

From: Radu Sabau <radu.sabau@analog.com>

Add buffered capture via the device's hardware FIFO - the only
capture mode this driver supports. Popping the FIFO uses a dedicated
command and payload format (FIFO_DATA_CMD, 52 bytes, no checksum),
handled directly rather than through adislib's generic burst-read
helpers, which assume one fixed-shape read per trigger rather than a
variable-count drain loop.

The watermark-met condition is signaled via DEV_NOK on the device's
one interrupt pin, which stays asserted (level-triggered) until
DIAG_STAT is read and cleared - matching adislib's has_fifo contract.
DEV_NOK fires on any DIAG_STAT flag, not just FIFO_THRESH_MET, so the
trigger handler checks that specific bit before popping anything.
DR_GPIO_MASK stays enabled unconditionally: DR pulses drive the FIFO
controller's internal sample enqueue, independent of pin routing.

Watermark is configurable via the standard hwfifo_set_watermark
callback and the usual hwfifo_* sysfs attributes.

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
 drivers/iio/imu/adis16607.c | 392 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 388 insertions(+), 4 deletions(-)

diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
index 5e2c999e68ac..bd7752f7a1ec 100644
--- a/drivers/iio/imu/adis16607.c
+++ b/drivers/iio/imu/adis16607.c
@@ -13,6 +13,10 @@
 #include <linux/iio/buffer.h>
 #include <linux/iio/iio.h>
 #include <linux/iio/imu/adis.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
 #include <linux/math.h>
 #include <linux/minmax.h>
 #include <linux/module.h>
@@ -43,8 +47,11 @@
 #define ADIS16607_REG_Y_DELTANG		0x1A
 #define ADIS16607_REG_Z_DELTANG		0x1C
 #define ADIS16607_REG_TEMP		0x20
+#define ADIS16607_REG_FIFO_WORD_CNT	0x2B
 #define ADIS16607_REG_USER_GPIO_CFG	0x2F
 #define ADIS16607_REG_SPI_HALFDUPLEX	0x32
+#define ADIS16607_REG_USER_DATA_CFG	0x34
+#define ADIS16607_REG_USER_FIFO_CFG	0x35
 #define ADIS16607_REG_SOFT_RESET	0x36
 #define ADIS16607_REG_SELF_TEST		0x39
 #define ADIS16607_REG_DEC_RATE		0x3A
@@ -55,8 +62,29 @@
 
 /* Masks */
 #define ADIS16607_RESET_GPIO_MASK	GENMASK(2, 0)
+#define ADIS16607_DR_GPIO_MASK		GENMASK(11, 9)
+#define ADIS16607_DEV_NOK_MASK		GENMASK(14, 12)
+#define ADIS16607_DATA_CNTR_EN_MASK	BIT(14)
+#define ADIS16607_BURST32_MASK		BIT(15)
 #define ADIS16607_BOOTLOADER_BUSY_MASK	BIT(0)
 
+/* FIFO masks */
+#define ADIS16607_FIFO_THR_MASK		GENMASK(10, 0)
+#define ADIS16607_FIFO_FLUSH_MASK	BIT(15)
+#define ADIS16607_FIFO_THR_MET_MASK	BIT(0)
+#define ADIS16607_FIFO_DATA_CMD		0xA9
+
+/* FIFO limits */
+#define ADIS16607_MAX_FIFO_WM		38
+#define ADIS16607_FIFO_WORDS_PER_SAMPLE	26
+#define ADIS16607_FIFO_DATA_LEN		52
+
+/* FIFO_DATA read - Half-Duplex: 2-byte command phase, then the FIFO data */
+#define ADIS16607_FIFO_DATA_CMD_SIZE	2
+
+/* Largest scan: 12 channels of 24 bits stored as 32, plus 16-bit temperature */
+#define ADIS16607_SCAN_DATA_SIZE	(12 * sizeof(s32) + sizeof(s16))
+
 /* Self-test registers and masks */
 #define ADIS16607_REG_SELF_TEST_DATA(x)	((x) + 0x23)
 #define ADIS16607_SNSR_SELF_TEST_MASK	BIT(6)
@@ -87,6 +115,12 @@ struct adis16607 {
 	const struct adis16607_chip_info *info;
 	unsigned long clk_freq_mHz;
 	u64 sample_period_ns;
+	u16 fifo_watermark;
+	/* IIO buffer for reordered data - sized for max channels */
+	IIO_DECLARE_BUFFER_WITH_TS(u8, data, ADIS16607_SCAN_DATA_SIZE);
+	/* Scratch buffer used for FIFO pop operations, see adis16607_fifo_pop() */
+	u8 fifo_buf[ADIS16607_FIFO_DATA_CMD_SIZE + ADIS16607_FIFO_DATA_LEN]
+		__aligned(IIO_DMA_MINALIGN);
 };
 
 enum {
@@ -229,6 +263,304 @@ static const struct adis_ops adis16607_ops = {
 	.reset = adis16607_reset,
 };
 
+/*
+ * FIFO/burst data offsets for each channel (in bytes from the data start,
+ * i.e. after the 4-byte command prefix).
+ * Each 32-bit inertial channel occupies 4 bytes (MSW + LSW), TEMP is 2 bytes.
+ *
+ * FIFO data layout (52 bytes, DATA_CNTR enabled, no checksum):
+ *   Offset 0-3:   ACCEL_X  (32-bit: MSW, LSW)
+ *   Offset 4-7:   ACCEL_Y
+ *   Offset 8-11:  ACCEL_Z
+ *   Offset 12-15: GYRO_X
+ *   Offset 16-19: GYRO_Y
+ *   Offset 20-23: GYRO_Z
+ *   Offset 24-27: DELTVEL_X
+ *   Offset 28-31: DELTVEL_Y
+ *   Offset 32-35: DELTVEL_Z
+ *   Offset 36-39: DELTANG_X
+ *   Offset 40-43: DELTANG_Y
+ *   Offset 44-47: DELTANG_Z
+ *   Offset 48-49: TEMP (16-bit)
+ *   Offset 50-51: DATA_CNTR (16-bit)
+ *
+ * Each 32-bit value encodes 24-bit sensor data as:
+ *   value_24bit = (MSW << 8) | (LSW >> 8)
+ */
+static const u8 adis16607_burst_offsets[] = {
+	[ADIS16607_SCAN_ACCEL_X] = 0,
+	[ADIS16607_SCAN_ACCEL_Y] = 4,
+	[ADIS16607_SCAN_ACCEL_Z] = 8,
+	[ADIS16607_SCAN_GYRO_X] = 12,
+	[ADIS16607_SCAN_GYRO_Y] = 16,
+	[ADIS16607_SCAN_GYRO_Z] = 20,
+	[ADIS16607_SCAN_DELTVEL_X] = 24,
+	[ADIS16607_SCAN_DELTVEL_Y] = 28,
+	[ADIS16607_SCAN_DELTVEL_Z] = 32,
+	[ADIS16607_SCAN_DELTANG_X] = 36,
+	[ADIS16607_SCAN_DELTANG_Y] = 40,
+	[ADIS16607_SCAN_DELTANG_Z] = 44,
+	[ADIS16607_SCAN_TEMP] = 48,
+};
+
+/*
+ * Pop a single sample from FIFO using FIFO_DATA command (0xA9).
+ * Returns the raw FIFO data in the provided buffer.
+ */
+static int adis16607_fifo_pop(struct adis16607 *st, u8 *data)
+{
+	struct spi_message msg;
+	struct spi_transfer xfer = {
+		.tx_buf = st->fifo_buf,
+		.rx_buf = st->fifo_buf,
+		.len = ADIS16607_FIFO_DATA_CMD_SIZE + ADIS16607_FIFO_DATA_LEN,
+	};
+	int ret;
+
+	/* FIFO data command */
+	st->fifo_buf[0] = ADIS16607_FIFO_DATA_CMD;
+
+	spi_message_init(&msg);
+	spi_message_add_tail(&xfer, &msg);
+
+	ret = spi_sync(st->adis.spi, &msg);
+	if (ret)
+		return ret;
+
+	/* Copy data starting after command bytes */
+	memcpy(data, st->fifo_buf + ADIS16607_FIFO_DATA_CMD_SIZE, ADIS16607_FIFO_DATA_LEN);
+	return 0;
+}
+
+/*
+ * Process a single FIFO sample and push to IIO buffer.
+ * FIFO data format differs from regular burst - no DIAG_STAT, data starts immediately.
+ *
+ * FIFO data layout (52 bytes):
+ *   Offset 0-3:   ACCEL_X (32-bit)
+ *   Offset 4-7:   ACCEL_Y (32-bit)
+ *   Offset 8-11:  ACCEL_Z (32-bit)
+ *   Offset 12-15: GYRO_X (32-bit)
+ *   Offset 16-19: GYRO_Y (32-bit)
+ *   Offset 20-23: GYRO_Z (32-bit)
+ *   Offset 24-27: DELTVEL_X (32-bit)
+ *   Offset 28-31: DELTVEL_Y (32-bit)
+ *   Offset 32-35: DELTVEL_Z (32-bit)
+ *   Offset 36-39: DELTANG_X (32-bit)
+ *   Offset 40-43: DELTANG_Y (32-bit)
+ *   Offset 44-47: DELTANG_Z (32-bit)
+ *   Offset 48-49: TEMP (16-bit)
+ *   Offset 50-51: DATA_CNTR (16-bit)
+ */
+static void adis16607_fifo_push_sample(struct iio_dev *indio_dev,
+				       u8 *fifo_data, s64 timestamp)
+{
+	struct adis16607 *st = iio_priv(indio_dev);
+	u16 msw, lsw;
+	s32 val24;
+	int bit, offset = 0;
+
+	memset(st->data, 0, sizeof(st->data));
+
+	iio_for_each_active_channel(indio_dev, bit) {
+		u8 burst_off;
+
+		/* Skip the soft timestamp channel - it has no burst offset */
+		if (bit >= ARRAY_SIZE(adis16607_burst_offsets))
+			continue;
+
+		burst_off = adis16607_burst_offsets[bit];
+
+		if (bit == ADIS16607_SCAN_TEMP) {
+			/* TEMP is 16-bit */
+			put_unaligned_be16(get_unaligned_be16(&fifo_data[burst_off]),
+					   &st->data[offset]);
+			offset += 2;
+		} else {
+			/* All other channels are 24-bit stored as 32-bit */
+			msw = get_unaligned_be16(&fifo_data[burst_off]);
+			lsw = get_unaligned_be16(&fifo_data[burst_off + 2]);
+			val24 = sign_extend32(((u32)msw << 8) | (lsw >> 8), 23);
+			put_unaligned_be32(val24, &st->data[offset]);
+			offset += 4;
+		}
+	}
+
+	iio_push_to_buffers_with_ts(indio_dev, st->data, sizeof(st->data),
+				    timestamp);
+}
+
+/*
+ * Drain the device FIFO. Triggered by the DEV_NOK interrupt, which fires on
+ * any DIAG_STAT flag, not just FIFO_THRESH_MET - the DIAG_STAT read below
+ * both checks which flag(s) raised the interrupt and clears the latched
+ * status, and must happen before any FIFO pop, or we'd pop for nothing on
+ * an unrelated flag.
+ */
+static void adis16607_fifo_drain(struct iio_dev *indio_dev, s64 irq_timestamp)
+{
+	struct adis16607 *st = iio_priv(indio_dev);
+	u8 fifo_sample[ADIS16607_FIFO_DATA_LEN];
+	u16 diag_stat, word_cnt, sample_cnt;
+	s64 timestamp;
+	int ret;
+
+	adis_dev_auto_lock(&st->adis);
+
+	/* Check if FIFO threshold is met */
+	ret = __adis_read_reg_16(&st->adis, ADIS16607_REG_DIAG_STAT, &diag_stat);
+	if (ret)
+		return;
+
+	if (!(diag_stat & ADIS16607_FIFO_THR_MET_MASK))
+		return;
+
+	/* Read FIFO word count */
+	ret = __adis_read_reg_16(&st->adis, ADIS16607_REG_FIFO_WORD_CNT, &word_cnt);
+	if (ret)
+		return;
+
+	/* Calculate number of complete samples */
+	sample_cnt = word_cnt / ADIS16607_FIFO_WORDS_PER_SAMPLE;
+	if (!sample_cnt)
+		return;
+
+	/*
+	 * irq_timestamp is the IRQ-fire time, i.e. when the newest (last
+	 * enqueued) sample tipped the FIFO over watermark. The FIFO pops
+	 * oldest-first, so back-date the first popped sample's timestamp by
+	 * the batch duration - the loop then counts back up to irq_timestamp
+	 * for the newest sample, instead of projecting past it.
+	 */
+	timestamp = irq_timestamp - (s64)(sample_cnt - 1) * st->sample_period_ns;
+
+	/* Pop and process each sample */
+	for (unsigned int i = 0; i < sample_cnt; i++) {
+		ret = adis16607_fifo_pop(st, fifo_sample);
+		if (ret)
+			return;
+
+		adis16607_fifo_push_sample(indio_dev, fifo_sample, timestamp);
+		timestamp += st->sample_period_ns;
+	}
+}
+
+static irqreturn_t adis16607_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+
+	adis16607_fifo_drain(indio_dev, pf->timestamp);
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+/* FIFO sysfs attributes */
+IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_min, "1");
+IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_max, __stringify(ADIS16607_MAX_FIFO_WM));
+
+static ssize_t hwfifo_watermark_show(struct device *dev,
+				     struct device_attribute *attr,
+				     char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct adis16607 *st = iio_priv(indio_dev);
+
+	return sysfs_emit(buf, "%u\n", st->fifo_watermark);
+}
+
+static ssize_t hwfifo_enabled_show(struct device *dev,
+				   struct device_attribute *attr,
+				   char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct adis16607 *st = iio_priv(indio_dev);
+	u16 val;
+	int ret;
+
+	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_USER_FIFO_CFG, &val);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%d\n", (val & ADIS16607_FIFO_THR_MASK) ? 1 : 0);
+}
+
+static IIO_DEVICE_ATTR_RO(hwfifo_watermark, 0);
+static IIO_DEVICE_ATTR_RO(hwfifo_enabled, 0);
+
+static const struct iio_dev_attr *adis16607_fifo_attributes[] = {
+	&iio_dev_attr_hwfifo_watermark_min,
+	&iio_dev_attr_hwfifo_watermark_max,
+	&iio_dev_attr_hwfifo_watermark,
+	&iio_dev_attr_hwfifo_enabled,
+	NULL
+};
+
+static int adis16607_buffer_postenable(struct iio_dev *indio_dev)
+{
+	struct adis16607 *st = iio_priv(indio_dev);
+	struct adis *adis = &st->adis;
+	u16 threshold;
+	int ret;
+
+	adis_dev_auto_lock(adis);
+
+	/* Flush FIFO to start clean */
+	ret = __adis_write_reg_16(adis, ADIS16607_REG_USER_FIFO_CFG,
+				  ADIS16607_FIFO_FLUSH_MASK);
+	if (ret)
+		return ret;
+
+	/* Enable FIFO with watermark threshold (in word count) */
+	threshold = st->fifo_watermark * ADIS16607_FIFO_WORDS_PER_SAMPLE;
+	return __adis_write_reg_16(adis, ADIS16607_REG_USER_FIFO_CFG,
+				   threshold & ADIS16607_FIFO_THR_MASK);
+}
+
+static int adis16607_buffer_postdisable(struct iio_dev *indio_dev)
+{
+	struct adis16607 *st = iio_priv(indio_dev);
+	struct adis *adis = &st->adis;
+	u16 diag_stat;
+	int ret;
+
+	adis_dev_auto_lock(adis);
+
+	/* Disable FIFO */
+	ret = __adis_write_reg_16(adis, ADIS16607_REG_USER_FIFO_CFG, 0);
+	if (ret)
+		return ret;
+
+	/*
+	 * Leave the device clean at the end of a session: the core attaches
+	 * the trigger before calling postenable(), so the next session's
+	 * handler could otherwise run on leftover samples and a still-latched
+	 * FIFO_THR_MET.
+	 */
+	ret = __adis_write_reg_16(adis, ADIS16607_REG_USER_FIFO_CFG,
+				  ADIS16607_FIFO_FLUSH_MASK);
+	if (ret)
+		return ret;
+
+	return __adis_read_reg_16(adis, ADIS16607_REG_DIAG_STAT, &diag_stat);
+}
+
+static const struct iio_buffer_setup_ops adis16607_buffer_ops = {
+	.postenable = adis16607_buffer_postenable,
+	.postdisable = adis16607_buffer_postdisable,
+};
+
+static int adis16607_set_watermark(struct iio_dev *indio_dev, unsigned int val)
+{
+	struct adis16607 *st = iio_priv(indio_dev);
+
+	val = clamp(val, 1, ADIS16607_MAX_FIFO_WM);
+	st->fifo_watermark = val;
+
+	return 0;
+}
+
 /*
  * Check if the delta between two values is within specified range.
  */
@@ -316,7 +648,8 @@ static int adis16607_sensor_self_test(struct adis16607 *st)
 
 /*
  * Called by __adis_initial_startup() right after reset, before self-test:
- * SPI Half-Duplex locking, dev_id verification and the bootloader check.
+ * SPI Half-Duplex locking, dev_id verification, the bootloader check,
+ * enabling 32-bit burst mode + DATA_CNTR and the DR/DEV_NOK pin routing.
  */
 static int adis16607_init(struct adis *adis)
 {
@@ -357,7 +690,29 @@ static int adis16607_init(struct adis *adis)
 	if (digital_status & ADIS16607_BOOTLOADER_BUSY_MASK)
 		return dev_err_probe(dev, -EBUSY, "bootloader is busy\n");
 
-	return 0;
+	/* Enable 32-bit burst mode and DATA_CNTR - one-time, used by FIFO pops */
+	ret = __adis_update_bits_base(adis, ADIS16607_REG_USER_DATA_CFG,
+				      ADIS16607_BURST32_MASK | ADIS16607_DATA_CNTR_EN_MASK,
+				      ADIS16607_BURST32_MASK | ADIS16607_DATA_CNTR_EN_MASK, 2);
+	if (ret)
+		return ret;
+
+	/*
+	 * DR_GPIO_MASK and DEV_NOK_MASK are updated here rather than written
+	 * blind, so this doesn't clobber the RESET_GPIO_MASK bit already
+	 * configured earlier in probe(). Per datasheet, DR pulses are what
+	 * drive the FIFO controller's internal enqueue logic. DR and
+	 * DEV_NOK are separate physical pins, so enabling DR_GPIO_MASK here
+	 * does not put DR's full-sample-rate pulses onto the interrupt line
+	 * - DEV_NOK_MASK routes the aggregate "any DIAG_STAT flag asserted"
+	 * signal onto the one physically-wired interrupt pin, which is what
+	 * actually fires on FIFO_THRESH_MET, at the (much lower) watermark
+	 * rate, not the sample rate.
+	 */
+	return __adis_update_bits_base(adis, ADIS16607_REG_USER_GPIO_CFG,
+				       ADIS16607_DR_GPIO_MASK | ADIS16607_DEV_NOK_MASK,
+				       FIELD_PREP(ADIS16607_DR_GPIO_MASK, 1) |
+				       FIELD_PREP(ADIS16607_DEV_NOK_MASK, 1), 2);
 }
 
 /*
@@ -609,7 +964,12 @@ static int adis16607_write_raw(struct iio_dev *indio_dev,
 	u32 tmp;
 
 	switch (info) {
-	case IIO_CHAN_INFO_SAMP_FREQ:
+	case IIO_CHAN_INFO_SAMP_FREQ: {
+		/* The decimation must not change while the buffer is running */
+		IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+		if (IIO_DEV_ACQUIRE_FAILED(claim))
+			return -EBUSY;
+
 		if (val < 0 || val2 < 0)
 			return -EINVAL;
 
@@ -617,7 +977,7 @@ static int adis16607_write_raw(struct iio_dev *indio_dev,
 		tmp = val * MILLIHZ_PER_HZ +
 		      val2 / (MICROHZ_PER_HZ / MILLIHZ_PER_HZ);
 		return adis16607_set_freq(st, tmp);
-
+	}
 	default:
 		return -EINVAL;
 	}
@@ -716,6 +1076,7 @@ static const struct iio_info adis16607_info = {
 	.read_raw = adis16607_read_raw,
 	.write_raw = adis16607_write_raw,
 	.debugfs_reg_access = adis_debugfs_reg_access,
+	.hwfifo_set_watermark = adis16607_set_watermark,
 };
 
 static const char * const adis16607_status_error_msgs[] = {
@@ -737,6 +1098,7 @@ static const struct adis_data adis16607_data = {
 	.prod_id_reg = ADIS16607_REG_DEV_ID,
 	.prod_id = 0x6000,
 	.unmasked_drdy = true,
+	.has_fifo = true,
 	.init = adis16607_init,
 	.self_test = adis16607_self_test,
 	.status_error_msgs = adis16607_status_error_msgs,
@@ -800,6 +1162,7 @@ static int adis16607_probe(struct spi_device *spi)
 	struct device *dev = &spi->dev;
 	struct iio_dev *indio_dev;
 	struct adis16607 *st;
+	u32 irq_type;
 	int ret;
 
 	indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
@@ -817,6 +1180,8 @@ static int adis16607_probe(struct spi_device *spi)
 	indio_dev->info = &adis16607_info;
 	indio_dev->modes = INDIO_DIRECT_MODE;
 
+	st->fifo_watermark = ADIS16607_MAX_FIFO_WM;
+
 	st->adis.ops = &adis16607_ops;
 
 	ret = devm_regulator_get_enable(dev, "vdd");
@@ -855,6 +1220,25 @@ static int adis16607_probe(struct spi_device *spi)
 	if (ret)
 		return ret;
 
+	/*
+	 * DEV_NOK stays asserted until DIAG_STAT is read, so the interrupt
+	 * must be level-triggered, active high.
+	 */
+	irq_type = irq_get_trigger_type(spi->irq);
+	if (irq_type != IRQ_TYPE_LEVEL_HIGH)
+		return dev_err_probe(dev, -EINVAL,
+				     "Invalid interrupt type 0x%x specified\n",
+				     irq_type);
+
+	st->adis.irq_flag = IRQF_TRIGGER_HIGH;
+
+	ret = devm_adis_setup_buffer_and_trigger_with_attrs(&st->adis, indio_dev,
+							    adis16607_trigger_handler,
+							    &adis16607_buffer_ops,
+							    adis16607_fifo_attributes);
+	if (ret)
+		return ret;
+
 	ret = devm_iio_device_register(dev, indio_dev);
 	if (ret)
 		return ret;

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v5 7/8] iio: imu: adis16607: Add external clock sync support
  2026-11-02  9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
                   ` (5 preceding siblings ...)
  2026-11-02  9:25 ` [PATCH v5 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture Radu Sabau via B4 Relay
@ 2026-11-02  9:25 ` Radu Sabau via B4 Relay
  2026-11-02  9:42   ` sashiko-bot
  2026-11-02  9:25 ` [PATCH v5 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel Radu Sabau via B4 Relay
  7 siblings, 1 reply; 13+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02  9:25 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron

From: Radu Sabau <radu.sabau@analog.com>

Add support for an optional external clock (clocks property),
selecting between SYNC_DIRECT (1 kHz to 8 kHz) and SYNC_SCALED (1 Hz
to 400 Hz) depending on the supplied frequency, with the internal
8 kHz clock used as the default when no clock is provided.

The clock rate is requested as exclusive since the sample period and
the sync scale are derived from it. In scaled mode the sync scale is
chosen when the sampling frequency is set, so that the requested rate
is matched as closely as possible while keeping the clock multiplied
at or below the internal rate. Loss of sync or of the PLL lock is
reported from the trigger handler, as the device keeps sampling
without it.

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
 drivers/iio/imu/adis16607.c | 219 +++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 206 insertions(+), 13 deletions(-)

diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
index bd7752f7a1ec..58504d351de1 100644
--- a/drivers/iio/imu/adis16607.c
+++ b/drivers/iio/imu/adis16607.c
@@ -8,6 +8,7 @@
 #include <linux/bitfield.h>
 #include <linux/bitops.h>
 #include <linux/build_bug.h>
+#include <linux/clk.h>
 #include <linux/debugfs.h>
 #include <linux/delay.h>
 #include <linux/iio/buffer.h>
@@ -50,6 +51,7 @@
 #define ADIS16607_REG_FIFO_WORD_CNT	0x2B
 #define ADIS16607_REG_USER_GPIO_CFG	0x2F
 #define ADIS16607_REG_SPI_HALFDUPLEX	0x32
+#define ADIS16607_REG_USER_SYNC		0x33
 #define ADIS16607_REG_USER_DATA_CFG	0x34
 #define ADIS16607_REG_USER_FIFO_CFG	0x35
 #define ADIS16607_REG_SOFT_RESET	0x36
@@ -62,12 +64,18 @@
 
 /* Masks */
 #define ADIS16607_RESET_GPIO_MASK	GENMASK(2, 0)
+#define ADIS16607_SYNC_GPIO_MASK	GENMASK(8, 6)
 #define ADIS16607_DR_GPIO_MASK		GENMASK(11, 9)
 #define ADIS16607_DEV_NOK_MASK		GENMASK(14, 12)
 #define ADIS16607_DATA_CNTR_EN_MASK	BIT(14)
 #define ADIS16607_BURST32_MASK		BIT(15)
+#define ADIS16607_SYNC_MODE_MASK	BIT(15)
 #define ADIS16607_BOOTLOADER_BUSY_MASK	BIT(0)
 
+/* DIAG_STAT sync flags */
+#define ADIS16607_DIAG_DPLL_UNLOCKED	BIT(1)
+#define ADIS16607_DIAG_SYNC_LOST	BIT(2)
+
 /* FIFO masks */
 #define ADIS16607_FIFO_THR_MASK		GENMASK(10, 0)
 #define ADIS16607_FIFO_FLUSH_MASK	BIT(15)
@@ -95,8 +103,22 @@
 #define ADIS16607_ACCEL_Z_DELTA_MAX	4000
 #define ADIS16607_GYRO_DELTA_MAX	2600
 
+/* Sync modes */
+enum adis16607_sync_mode {
+	ADIS16607_SYNC_DEFAULT,
+	ADIS16607_SYNC_DIRECT,
+	ADIS16607_SYNC_SCALED,
+};
+
+struct adis16607_sync {
+	u16 sync_mode;
+	u16 min_rate;
+	u16 max_rate;
+};
+
 struct adis16607_chip_info {
 	const struct iio_chan_spec *channels;
+	const struct adis16607_sync *sync_mode;
 	const char *name;
 	u32 num_channels;
 	u32 gyro_max_val;
@@ -108,6 +130,7 @@ struct adis16607_chip_info {
 	u32 deltvel_max_val;
 	u32 int_clk_Hz;
 	u16 max_dec;
+	u8 num_sync;
 };
 
 struct adis16607 {
@@ -115,6 +138,9 @@ struct adis16607 {
 	const struct adis16607_chip_info *info;
 	unsigned long clk_freq_mHz;
 	u64 sample_period_ns;
+	unsigned long ext_clk_Hz;
+	u32 sync_mode;
+	u16 sync_scale;
 	u16 fifo_watermark;
 	/* IIO buffer for reordered data - sized for max channels */
 	IIO_DECLARE_BUFFER_WITH_TS(u8, data, ADIS16607_SCAN_DATA_SIZE);
@@ -407,11 +433,18 @@ static void adis16607_fifo_drain(struct iio_dev *indio_dev, s64 irq_timestamp)
 
 	adis_dev_auto_lock(&st->adis);
 
-	/* Check if FIFO threshold is met */
 	ret = __adis_read_reg_16(&st->adis, ADIS16607_REG_DIAG_STAT, &diag_stat);
 	if (ret)
 		return;
 
+	/* The device keeps sampling without sync, so only warn about it */
+	if (diag_stat & ADIS16607_DIAG_SYNC_LOST)
+		dev_warn_ratelimited(&st->adis.spi->dev, "external sync lost\n");
+
+	if (diag_stat & ADIS16607_DIAG_DPLL_UNLOCKED)
+		dev_warn_ratelimited(&st->adis.spi->dev, "sync PLL unlocked\n");
+
+	/* Check if FIFO threshold is met */
 	if (!(diag_stat & ADIS16607_FIFO_THR_MET_MASK))
 		return;
 
@@ -841,8 +874,44 @@ static int adis16607_get_freq(struct adis16607 *st, u32 *freq)
 	return 0;
 }
 
+/*
+ * In scaled sync mode the output rate is ext_clk * scale / (dec + 1). Find the
+ * (scale, dec) pair closest to the requested rate (in mHz), keeping
+ * ext_clk * scale at or below the internal rate. On a tie the higher scale
+ * wins, so the decimation filter averages more samples.
+ */
+static void adis16607_scaled_params(struct adis16607 *st, u32 freq,
+				    u16 *scale, u32 *dec)
+{
+	u32 max_scale = st->info->int_clk_Hz / st->ext_clk_Hz;
+	u32 max_dec = st->info->max_dec + 1;
+	u64 best_err = U64_MAX;
+	u64 target = freq;
+
+	for (u32 k = 1; k <= max_scale; k++) {
+		u64 rate = st->ext_clk_Hz;
+		u64 div, odr, err;
+
+		rate *= k;
+		rate *= MILLIHZ_PER_HZ;
+
+		div = DIV_ROUND_CLOSEST_ULL(rate, freq);
+		div = clamp(div, 1, max_dec);
+		odr = DIV_ROUND_CLOSEST_ULL(rate, div);
+		err = abs_diff(odr, target);
+		if (err > best_err)
+			continue;
+
+		best_err = err;
+		*scale = k;
+		*dec = div - 1;
+	}
+}
+
 static int adis16607_set_freq(struct adis16607 *st, u32 freq)
 {
+	unsigned long clk_freq_mHz = st->clk_freq_mHz;
+	u16 scale = st->sync_scale;
 	u64 period_ns;
 	u32 dec;
 	int ret;
@@ -850,11 +919,16 @@ static int adis16607_set_freq(struct adis16607 *st, u32 freq)
 	if (!freq)
 		return -EINVAL;
 
-	dec = DIV_ROUND_CLOSEST(st->clk_freq_mHz, freq);
-	if (dec)
-		dec--;
+	if (st->sync_mode == ADIS16607_SYNC_SCALED) {
+		adis16607_scaled_params(st, freq, &scale, &dec);
+		clk_freq_mHz = st->ext_clk_Hz * scale * MILLIHZ_PER_HZ;
+	} else {
+		dec = DIV_ROUND_CLOSEST(clk_freq_mHz, freq);
+		if (dec)
+			dec--;
 
-	dec = min(dec, st->info->max_dec);
+		dec = min(dec, st->info->max_dec);
+	}
 
 	/*
 	 * The sample period is (dec + 1) / clk_freq. clk_freq_mHz is in
@@ -865,7 +939,7 @@ static int adis16607_set_freq(struct adis16607 *st, u32 freq)
 	period_ns = NSEC_PER_SEC;
 	period_ns *= MILLIHZ_PER_HZ;
 	period_ns *= dec + 1;
-	period_ns = DIV_ROUND_CLOSEST_ULL(period_ns, st->clk_freq_mHz);
+	period_ns = DIV_ROUND_CLOSEST_ULL(period_ns, clk_freq_mHz);
 
 	adis_dev_auto_scoped_lock(&st->adis) {
 		/* Reset the decimation accumulator before the new value */
@@ -873,6 +947,21 @@ static int adis16607_set_freq(struct adis16607 *st, u32 freq)
 		if (ret)
 			return ret;
 
+		/*
+		 * Writing USER_SYNC restarts the PLL lock, so only do it when
+		 * the scale actually changes.
+		 */
+		if (scale != st->sync_scale) {
+			ret = __adis_write_reg_16(&st->adis,
+						  ADIS16607_REG_USER_SYNC,
+						  ADIS16607_SYNC_MODE_MASK | scale);
+			if (ret)
+				return ret;
+
+			st->sync_scale = scale;
+			st->clk_freq_mHz = clk_freq_mHz;
+		}
+
 		if (dec) {
 			ret = __adis_write_reg_16(&st->adis,
 						  ADIS16607_REG_DEC_RATE, dec);
@@ -1079,6 +1168,17 @@ static const struct iio_info adis16607_info = {
 	.hwfifo_set_watermark = adis16607_set_watermark,
 };
 
+/*
+ * External sync clock ranges in Hz. The datasheet gives 0.8 Hz as the scaled
+ * mode minimum, but clk_get_rate() reports whole Hz so 1 Hz is the lowest
+ * rate we can match.
+ */
+static const struct adis16607_sync adis16607_sync_modes[] = {
+	{ ADIS16607_SYNC_DEFAULT, 0, 0 },
+	{ ADIS16607_SYNC_DIRECT, 1000, 8000 },
+	{ ADIS16607_SYNC_SCALED, 1, 400 },
+};
+
 static const char * const adis16607_status_error_msgs[] = {
 	[9] = "Boot Memory Failure",
 	[11] = "Power Supply Failure",
@@ -1123,6 +1223,8 @@ static const struct adis16607_chip_info adis16607_2_chip_info = {
 	.deltvel_max_val = IIO_G_TO_M_S_2(40),
 	.int_clk_Hz	= 8000,
 	.max_dec	= 65535,
+	.sync_mode	= adis16607_sync_modes,
+	.num_sync	= ARRAY_SIZE(adis16607_sync_modes),
 };
 
 static const struct adis16607_chip_info adis16607_3_chip_info = {
@@ -1138,22 +1240,113 @@ static const struct adis16607_chip_info adis16607_3_chip_info = {
 	.deltvel_max_val = IIO_G_TO_M_S_2(40),
 	.int_clk_Hz	= 8000,
 	.max_dec	= 65535,
+	.sync_mode	= adis16607_sync_modes,
+	.num_sync	= ARRAY_SIZE(adis16607_sync_modes),
 };
 
-/*
- * No external clock sync support yet: always run on the internal clock.
- * Default dec=0 after reset, so period = 1 / clk_freq_mHz.
- */
 static int adis16607_config_clk(struct adis16607 *st)
 {
-	u64 period_ns = NSEC_PER_SEC;
+	const struct adis16607_sync *sync_mode_data = NULL;
+	struct device *dev = &st->adis.spi->dev;
+	struct clk *clk;
+	u64 period_ns;
+	u16 dec;
+	int ret;
+
+	clk = devm_clk_get_optional_enabled(dev, NULL);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	if (clk) {
+		/* The sync scale and sample period depend on this rate */
+		ret = devm_clk_rate_exclusive_get(dev, clk);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "Failed to get exclusive clk rate\n");
+
+		st->ext_clk_Hz = clk_get_rate(clk);
+
+		for (unsigned int i = 1; i < st->info->num_sync; i++) {
+			if (st->ext_clk_Hz >= st->info->sync_mode[i].min_rate &&
+			    st->ext_clk_Hz <= st->info->sync_mode[i].max_rate) {
+				sync_mode_data = &st->info->sync_mode[i];
+				break;
+			}
+		}
+
+		if (!sync_mode_data)
+			return dev_err_probe(dev, -EINVAL,
+					     "Clk rate: %lu not in a valid range\n",
+					     st->ext_clk_Hz);
+
+		st->sync_mode = sync_mode_data->sync_mode;
+	} else {
+		st->sync_mode = ADIS16607_SYNC_DEFAULT;
+	}
+
+	/*
+	 * Write the full desired sync state explicitly for every mode,
+	 * rather than only when enabling non-default sync: USER_SYNC's
+	 * documented reset default (0x0000, direct mode / scale 0) means
+	 * this isn't fixing a known reset-state issue, but each mode
+	 * establishing its own complete state here, instead of only
+	 * touching bits conditionally, is simpler to reason about and
+	 * doesn't depend on that default holding true.
+	 */
+	ret = adis_update_bits_base(&st->adis, ADIS16607_REG_USER_GPIO_CFG,
+				    ADIS16607_SYNC_GPIO_MASK,
+				    FIELD_PREP(ADIS16607_SYNC_GPIO_MASK,
+					       st->sync_mode != ADIS16607_SYNC_DEFAULT), 2);
+	if (ret)
+		return ret;
+
+	/*
+	 * Start at the highest scale that keeps ext_clk * scale at or below
+	 * the internal rate, which the datasheet says the part performs best
+	 * with. set_freq() picks the scale from then on.
+	 */
+	if (st->sync_mode == ADIS16607_SYNC_SCALED)
+		st->sync_scale = st->info->int_clk_Hz / st->ext_clk_Hz;
 
-	st->clk_freq_mHz = st->info->int_clk_Hz * MILLIHZ_PER_HZ;
+	ret = adis_write_reg_16(&st->adis, ADIS16607_REG_USER_SYNC,
+				(st->sync_mode == ADIS16607_SYNC_SCALED) ?
+				ADIS16607_SYNC_MODE_MASK | st->sync_scale : 0);
+	if (ret)
+		return ret;
 
-	/* clk_freq_mHz is in millihertz: scale to get the period in ns */
+	switch (st->sync_mode) {
+	case ADIS16607_SYNC_SCALED:
+		/*
+		 * The effective rate is ext_clk * sync_scale, not necessarily
+		 * int_clk_Hz - use the real value here so sample_period_ns (and
+		 * therefore FIFO sample timestamps) don't drift from it.
+		 */
+		st->clk_freq_mHz = st->ext_clk_Hz * st->sync_scale *
+				   MILLIHZ_PER_HZ;
+		break;
+	case ADIS16607_SYNC_DIRECT:
+		/* DIRECT: external clock is the effective internal rate */
+		st->clk_freq_mHz = st->ext_clk_Hz * MILLIHZ_PER_HZ;
+		break;
+	default:
+		st->clk_freq_mHz = st->info->int_clk_Hz * MILLIHZ_PER_HZ;
+		break;
+	}
+
+	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, &dec);
+	if (ret)
+		return ret;
+
+	/*
+	 * Sample period is (dec + 1) / clk_freq, with clk_freq_mHz in
+	 * millihertz. Same u64-first scheme as adis16607_set_freq().
+	 */
+	period_ns = NSEC_PER_SEC;
 	period_ns *= MILLIHZ_PER_HZ;
+	period_ns *= dec + 1;
 	st->sample_period_ns = DIV_ROUND_CLOSEST_ULL(period_ns,
 						     st->clk_freq_mHz);
+
 	return 0;
 }
 

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v5 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel
  2026-11-02  9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
                   ` (6 preceding siblings ...)
  2026-11-02  9:25 ` [PATCH v5 7/8] iio: imu: adis16607: Add external clock sync support Radu Sabau via B4 Relay
@ 2026-11-02  9:25 ` Radu Sabau via B4 Relay
  7 siblings, 0 replies; 13+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02  9:25 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron

From: Radu Sabau <radu.sabau@analog.com>

Add IIO_CHAN_INFO_CALIBBIAS support for the gyroscope and
accelerometer axes, backed by the device's per-axis bias registers
(XA/YA/ZA_BIAS, XG/YG/ZG_BIAS).

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
 drivers/iio/imu/adis16607.c | 39 ++++++++++++++++++++++++++++++++-------
 1 file changed, 32 insertions(+), 7 deletions(-)

diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
index 58504d351de1..ab80e0909bf6 100644
--- a/drivers/iio/imu/adis16607.c
+++ b/drivers/iio/imu/adis16607.c
@@ -57,6 +57,12 @@
 #define ADIS16607_REG_SOFT_RESET	0x36
 #define ADIS16607_REG_SELF_TEST		0x39
 #define ADIS16607_REG_DEC_RATE		0x3A
+#define ADIS16607_REG_XA_BIAS		0x3B
+#define ADIS16607_REG_YA_BIAS		0x3C
+#define ADIS16607_REG_ZA_BIAS		0x3D
+#define ADIS16607_REG_XG_BIAS		0x3E
+#define ADIS16607_REG_YG_BIAS		0x3F
+#define ADIS16607_REG_ZG_BIAS		0x40
 #define ADIS16607_REG_DIGITAL_STATUS	0x4E
 
 /* SPI Half-Duplex lock value */
@@ -168,15 +174,16 @@ enum {
 
 struct adis16607_chan_regs {
 	u16 raw_reg;
+	u16 calib_bias_reg;	/* 0 if the channel has no calibbias support */
 };
 
 static const struct adis16607_chan_regs adis16607_chan_regs[] = {
-	[ADIS16607_SCAN_ACCEL_X]   = { ADIS16607_REG_X_ACCEL },
-	[ADIS16607_SCAN_ACCEL_Y]   = { ADIS16607_REG_Y_ACCEL },
-	[ADIS16607_SCAN_ACCEL_Z]   = { ADIS16607_REG_Z_ACCEL },
-	[ADIS16607_SCAN_GYRO_X]    = { ADIS16607_REG_X_GYRO },
-	[ADIS16607_SCAN_GYRO_Y]    = { ADIS16607_REG_Y_GYRO },
-	[ADIS16607_SCAN_GYRO_Z]    = { ADIS16607_REG_Z_GYRO },
+	[ADIS16607_SCAN_ACCEL_X]   = { ADIS16607_REG_X_ACCEL, ADIS16607_REG_XA_BIAS },
+	[ADIS16607_SCAN_ACCEL_Y]   = { ADIS16607_REG_Y_ACCEL, ADIS16607_REG_YA_BIAS },
+	[ADIS16607_SCAN_ACCEL_Z]   = { ADIS16607_REG_Z_ACCEL, ADIS16607_REG_ZA_BIAS },
+	[ADIS16607_SCAN_GYRO_X]    = { ADIS16607_REG_X_GYRO, ADIS16607_REG_XG_BIAS },
+	[ADIS16607_SCAN_GYRO_Y]    = { ADIS16607_REG_Y_GYRO, ADIS16607_REG_YG_BIAS },
+	[ADIS16607_SCAN_GYRO_Z]    = { ADIS16607_REG_Z_GYRO, ADIS16607_REG_ZG_BIAS },
 	[ADIS16607_SCAN_DELTVEL_X] = { ADIS16607_REG_X_DELTVEL },
 	[ADIS16607_SCAN_DELTVEL_Y] = { ADIS16607_REG_Y_DELTVEL },
 	[ADIS16607_SCAN_DELTVEL_Z] = { ADIS16607_REG_Z_DELTVEL },
@@ -1030,6 +1037,16 @@ static int adis16607_read_raw(struct iio_dev *indio_dev,
 		/* Temperature: 0 raw counts = 25°C; offset = 25000 mdeg / 5 mdeg per LSB = 5000 */
 		*val = 5000;
 		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_CALIBBIAS: {
+		u16 reg = adis16607_chan_regs[chan->address].calib_bias_reg;
+		u16 tmp16;
+
+		ret = adis_read_reg_16(&st->adis, reg, &tmp16);
+		if (ret)
+			return ret;
+		*val = sign_extend32(tmp16, 15);
+		return IIO_VAL_INT;
+	}
 	case IIO_CHAN_INFO_SAMP_FREQ:
 		ret = adis16607_get_freq(st, &tmp);
 		if (ret)
@@ -1067,6 +1084,13 @@ static int adis16607_write_raw(struct iio_dev *indio_dev,
 		      val2 / (MICROHZ_PER_HZ / MILLIHZ_PER_HZ);
 		return adis16607_set_freq(st, tmp);
 	}
+	case IIO_CHAN_INFO_CALIBBIAS: {
+		u16 reg = adis16607_chan_regs[chan->address].calib_bias_reg;
+
+		if (val < S16_MIN || val > S16_MAX)
+			return -EINVAL;
+		return adis_write_reg_16(&st->adis, reg, val);
+	}
 	default:
 		return -EINVAL;
 	}
@@ -1081,7 +1105,8 @@ static int adis16607_write_raw(struct iio_dev *indio_dev,
 		.type = (_type),					       \
 		.modified = 1,						       \
 		.channel2 = (_mod),					       \
-		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		       \
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |		       \
+				      BIT(IIO_CHAN_INFO_CALIBBIAS),	       \
 		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),	       \
 		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),       \
 		.address = (_idx),					       \

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH v5 1/8] iio: imu: adis: Fix custom reset dispatch in adislib
  2026-11-02  9:25 ` [PATCH v5 1/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
@ 2026-11-02  9:38   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-11-02  9:38 UTC (permalink / raw)
  To: Radu Sabau; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] iio: imu: adis: incorrect Fixes tag and stale changelog in reset dispatch fix
--

commit 5c3f86b0c40a0ca82129c2f9586aae278b0f7d2a
Author: Radu Sabau <radu.sabau@analog.com>

iio: imu: adis: Fix custom reset dispatch in adislib

This commit updates __adis_initial_startup() and adis_reset() to dispatch
software resets through adis->ops->reset() rather than calling
__adis_reset() directly, and moves the sw_reset_ms settle delay out of
__adis_reset() into both callers.

>     iio: imu: adis: Fix custom reset dispatch in adislib
>     
>     __adis_initial_startup()'s no-reset-gpio path called __adis_reset()
>     directly instead of adis->ops->reset(), silently ignoring any driver's
>     custom reset implementation. Invisible until now since
>     adis_default_ops.reset is __adis_reset() itself. Dispatch through
>     adis->ops->reset() instead - the same bug existed in the public
>     adis_reset() wrapper, fixed here too.
>     
>     Move the settle-time sleep out of __adis_reset() and into both call
>     sites instead, so every reset op (default or custom) is treated
>     uniformly: adis->ops->reset() is expected to just do the write, and
>     the library always sleeps the settle time afterwards. This removes
>     the previous asymmetric "sleep here only for custom ops" special
>     case entirely.
>     
>     Fixes: ecb010d44108 ("iio: imu: adis: Refactor adis_initial_startup")
>     Reviewed-by: Nuno Sá <nuno.sa@analog.com>
>     Signed-off-by: Radu Sabau <radu.sabau@analog.com>

[Severity: Low]
Should the Fixes tag reference the commit that introduced the custom reset
callback instead?

Commit ecb010d44108 ("iio: imu: adis: Refactor adis_initial_startup")
predates struct adis_ops (added in commit 3b29bcee8f6f ("iio: imu: adis:
Add custom ops struct")). The custom reset callback adis->ops->reset was
introduced in commit 7f15d7a7d12d ("iio: imu: adis: Add reset to custom
ops"), which added reset to struct adis_ops without updating
__adis_initial_startup() or adis_reset() to call adis->ops->reset(). Should
this use:

  Fixes: 7f15d7a7d12d ("iio: imu: adis: Add reset to custom ops")

Also, is the commit message description accurate for the current baseline
tree?

The first paragraph mentions that the issue was invisible until now, but
drivers/iio/imu/adis16550.c already defines a custom reset callback in
adis16550_ops:

static const struct adis_ops adis16550_ops = {
	.write = adis16550_spi_write,
	.read = adis16550_spi_read,
	.reset = adis16550_reset,
};

In addition, the second paragraph states that this change removes a
previous asymmetric "sleep here only for custom ops" special case, which
does not exist in the baseline code prior to this patch. Could the commit
message be updated to reflect the current tree state?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-adis16607-v5-0-4f98b0d271ac@analog.com?part=1

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v5 7/8] iio: imu: adis16607: Add external clock sync support
  2026-11-02  9:25 ` [PATCH v5 7/8] iio: imu: adis16607: Add external clock sync support Radu Sabau via B4 Relay
@ 2026-11-02  9:42   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-11-02  9:42 UTC (permalink / raw)
  To: Radu Sabau; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] iio: imu: adis16607: unlocked clk_freq_mHz read in adis16607_get_freq()
- [Medium] iio: imu: adis16607: stale sample_period_ns on error in adis16607_set_freq()
--

commit d3f4ee30496626b77ece21c139b6d2175717f5c5
Author: Radu Sabau <radu.sabau@analog.com>

iio: imu: adis16607: Add external clock sync support

This commit adds support for an optional external sync clock on the
adis16607 IMU, selecting between direct sync mode (1 kHz to 8 kHz) and
scaled sync mode (1 Hz to 400 Hz) based on the supplied clock frequency.
In scaled mode, it dynamically selects the sync scale and decimation rate
when setting the sampling frequency, and reports sync or PLL lock loss from
the FIFO drain handler.

> diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
> index bd7752f7a1ec8..58504d351de16 100644
> --- a/drivers/iio/imu/adis16607.c
> +++ b/drivers/iio/imu/adis16607.c

[ ... ]

> @@ -873,6 +947,21 @@ static int adis16607_set_freq(struct adis16607 *st, u32 freq)
>  		if (ret)
>  			return ret;
>  
> +		/*
> +		 * Writing USER_SYNC restarts the PLL lock, so only do it when
> +		 * the scale actually changes.
> +		 */
> +		if (scale != st->sync_scale) {
> +			ret = __adis_write_reg_16(&st->adis,
> +						  ADIS16607_REG_USER_SYNC,
> +						  ADIS16607_SYNC_MODE_MASK | scale);
> +			if (ret)
> +				return ret;
> +
> +			st->sync_scale = scale;
> +			st->clk_freq_mHz = clk_freq_mHz;
> +		}

[Severity: Medium]
Now that st->clk_freq_mHz is updated at runtime under st->adis.state_lock in
adis16607_set_freq() when operating in ADIS16607_SYNC_SCALED mode, can a
concurrent sysfs read of sampling_frequency race with this update?

In adis16607_read_raw() -> adis16607_get_freq():

	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, &dec);
	if (ret)
		return ret;

	*freq = DIV_ROUND_CLOSEST(st->clk_freq_mHz, dec + 1);

adis_read_reg_16() releases st->adis.state_lock before st->clk_freq_mHz is
read, and adis16607_read_raw() does not claim IIO direct mode for
IIO_CHAN_INFO_SAMP_FREQ.

Could adis16607_get_freq() hold st->adis.state_lock across both
__adis_read_reg_16() and the division using st->clk_freq_mHz so it cannot
pair a stale or intermediate zero ADIS16607_REG_DEC_RATE value with the new
st->clk_freq_mHz (or vice versa)?

> +
>  		if (dec) {
>  			ret = __adis_write_reg_16(&st->adis,
>  						  ADIS16607_REG_DEC_RATE, dec);

[Severity: Medium]
In adis16607_set_freq(), __adis_write_reg_16(&st->adis,
ADIS16607_REG_DEC_RATE, 0) clears the hardware decimation rate to 0 at the
start of the critical section, and if scale != st->sync_scale,
ADIS16607_REG_USER_SYNC and st->clk_freq_mHz are also updated before the new
decimation rate is written:

	adis_dev_auto_scoped_lock(&st->adis) {
		/* Reset the decimation accumulator before the new value */
		ret = __adis_write_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, 0);
		if (ret)
			return ret;

		if (scale != st->sync_scale) {
			ret = __adis_write_reg_16(&st->adis,
						  ADIS16607_REG_USER_SYNC,
						  ADIS16607_SYNC_MODE_MASK | scale);
			if (ret)
				return ret;

			st->sync_scale = scale;
			st->clk_freq_mHz = clk_freq_mHz;
		}

		if (dec) {
			ret = __adis_write_reg_16(&st->adis,
						  ADIS16607_REG_DEC_RATE, dec);
			if (ret)
				return ret;
		}

		st->sample_period_ns = period_ns;
	}

If the write to ADIS16607_REG_USER_SYNC or the final write to
ADIS16607_REG_DEC_RATE fails, the hardware is left running with DEC_RATE = 0
(and possibly the new sync scale and st->clk_freq_mHz), while
st->sample_period_ns retains its old value from before the call.

Would this cause adis16607_fifo_drain() to compute skewed sample timestamps
on subsequent buffered reads?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-adis16607-v5-0-4f98b0d271ac@analog.com?part=7

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v5 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture
  2026-11-02  9:25 ` [PATCH v5 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture Radu Sabau via B4 Relay
@ 2026-11-02  9:47   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-11-02  9:47 UTC (permalink / raw)
  To: Radu Sabau; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] iio: imu: adis16607: level IRQ storm via has_fifo in adis16607_probe()
--

commit 4d8ecf6737569661c0f9a47d7a29ff4680559b17
Author: Radu Sabau <radu.sabau@analog.com>

iio: imu: adis16607: Add FIFO-based buffered/triggered capture

This commit adds buffered capture support to the adis16607 IMU driver using
the device's hardware FIFO, draining samples in a trigger handler signaled by
the level-high DEV_NOK interrupt and exposing watermark sysfs attributes.

> diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
> index 5e2c999e68ac5..bd7752f7a1ec8 100644
> --- a/drivers/iio/imu/adis16607.c
> +++ b/drivers/iio/imu/adis16607.c

[ ... ]

> @@ -229,6 +263,304 @@ static const struct adis_ops adis16607_ops = {

[ ... ]

> +/*
> + * Drain the device FIFO. Triggered by the DEV_NOK interrupt, which fires on
> + * any DIAG_STAT flag, not just FIFO_THRESH_MET - the DIAG_STAT read below
> + * both checks which flag(s) raised the interrupt and clears the latched
> + * status, and must happen before any FIFO pop, or we'd pop for nothing on
> + * an unrelated flag.
> + */
> +static void adis16607_fifo_drain(struct iio_dev *indio_dev, s64 irq_timestamp)
> +{
> +	struct adis16607 *st = iio_priv(indio_dev);
> +	u8 fifo_sample[ADIS16607_FIFO_DATA_LEN];
> +	u16 diag_stat, word_cnt, sample_cnt;
> +	s64 timestamp;
> +	int ret;
> +
> +	adis_dev_auto_lock(&st->adis);
> +
> +	/* Check if FIFO threshold is met */
> +	ret = __adis_read_reg_16(&st->adis, ADIS16607_REG_DIAG_STAT, &diag_stat);

[ ... ]

> +static irqreturn_t adis16607_trigger_handler(int irq, void *p)
> +{
> +	struct iio_poll_func *pf = p;
> +	struct iio_dev *indio_dev = pf->indio_dev;
> +
> +	adis16607_fifo_drain(indio_dev, pf->timestamp);
> +	iio_trigger_notify_done(indio_dev->trig);
> +
> +	return IRQ_HANDLED;
> +}

[ ... ]

> @@ -737,6 +1098,7 @@ static const struct adis_data adis16607_data = {
>  	.prod_id_reg = ADIS16607_REG_DEV_ID,
>  	.prod_id = 0x6000,
>  	.unmasked_drdy = true,
> +	.has_fifo = true,
>  	.init = adis16607_init,
>  	.self_test = adis16607_self_test,
>  	.status_error_msgs = adis16607_status_error_msgs,

[ ... ]

> @@ -855,6 +1220,25 @@ static int adis16607_probe(struct spi_device *spi)
>  	if (ret)
>  		return ret;
>  
> +	/*
> +	 * DEV_NOK stays asserted until DIAG_STAT is read, so the interrupt
> +	 * must be level-triggered, active high.
> +	 */
> +	irq_type = irq_get_trigger_type(spi->irq);
> +	if (irq_type != IRQ_TYPE_LEVEL_HIGH)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "Invalid interrupt type 0x%x specified\n",
> +				     irq_type);
> +
> +	st->adis.irq_flag = IRQF_TRIGGER_HIGH;
> +
> +	ret = devm_adis_setup_buffer_and_trigger_with_attrs(&st->adis, indio_dev,
> +							    adis16607_trigger_handler,
> +							    &adis16607_buffer_ops,
> +							    adis16607_fifo_attributes);

[Severity: High]
Does setting has_fifo = true in adis16607_data combined with a level-high
interrupt and devm_adis_setup_buffer_and_trigger_with_attrs() in
adis16607_probe() cause a kernel warning and a level-IRQ storm when buffered
capture is enabled?

When adis->data->has_fifo is set, devm_adis_probe_trigger() in
drivers/iio/imu/adis_trigger.c requests the physical SPI interrupt as a
threaded IRQ with iio_trigger_generic_data_rdy_poll() as the thread function:

	if (adis->data->has_fifo)
		ret = devm_request_threaded_irq(&adis->spi->dev, adis->spi->irq,
						NULL,
						&iio_trigger_generic_data_rdy_poll,
						adis->irq_flag | IRQF_ONESHOT,
						indio_dev->name,
						adis->trig);

Meanwhile, devm_adis_setup_buffer_and_trigger_with_attrs() registers
adis16607_trigger_handler() on a separate virtual subirq thread via
devm_iio_triggered_buffer_setup_ext() with iio_pollfunc_store_time() as its
top-half handler.

When the level-high DEV_NOK interrupt fires, irq_thread_fn() runs
iio_trigger_generic_data_rdy_poll() in process context with local CPU
interrupts enabled. That calls iio_trigger_poll() -> generic_handle_irq() ->
handle_simple_irq() -> handle_irq_event() -> __handle_irq_event_percpu(),
which runs iio_pollfunc_store_time() and then hits the hardirq-context check
in kernel/irq/handle.c:__handle_irq_event_percpu():

	if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n",
		      irq, action->handler))
		local_irq_disable();

More importantly, __handle_irq_event_percpu() only wakes the separate pollfunc
consumer thread for adis16607_trigger_handler() asynchronously and returns
immediately. iio_trigger_generic_data_rdy_poll() then returns IRQ_HANDLED to
irq_thread_fn(), which calls irq_finalize_oneshot() and unmasks the physical
level-high spi->irq line before adis16607_trigger_handler() ->
adis16607_fifo_drain() has run and read ADIS16607_REG_DIAG_STAT over SPI.

Because DEV_NOK remains asserted high until ADIS16607_REG_DIAG_STAT is read in
adis16607_fifo_drain(), unmasking spi->irq immediately after waking the
consumer thread causes the level-high interrupt to re-fire in an interrupt
storm (and can starve the consumer thread on a single CPU). In addition,
because no validate_trigger or validate_device callback is set, binding
another device to this trigger or vice versa would leave nothing reading
ADIS16607_REG_DIAG_STAT to clear DEV_NOK.

Since hardware FIFO capture is the only buffered mode supported by this
device, should adis16607_probe() register the buffer with
devm_iio_kfifo_buffer_setup_ext() and request the threaded IRQ directly
instead of going through an IIO trigger?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-adis16607-v5-0-4f98b0d271ac@analog.com?part=6

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v5 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607
  2026-11-02  9:25 ` [PATCH v5 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607 Radu Sabau via B4 Relay
@ 2026-11-02 19:36   ` Conor Dooley
  0 siblings, 0 replies; 13+ messages in thread
From: Conor Dooley @ 2026-11-02 19:36 UTC (permalink / raw)
  To: radu.sabau
  Cc: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Alexandru Ardelean, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-iio, linux-kernel,
	devicetree, linux

[-- Attachment #1: Type: text/plain, Size: 4904 bytes --]

On Mon, Nov 02, 2026 at 11:25:40AM +0200, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
> 
> Add device tree bindings for the Analog Devices ADIS16607 Precision
> MEMS Inertial Measurement Unit. The ADIS16607 integrates a triaxial
> gyroscope, triaxial accelerometer, and temperature sensor, and
> communicates over SPI Half-Duplex.
> 
> Two variants are supported, adis16607-2 and adis16607-3, differing
> only in gyroscope full-scale range (±450°/s and ±2000°/s
> respectively).
> 
> The binding exposes optional properties for:
>   - An external clock for sync mode (falling back to the internal 8 kHz
>     clock when absent)
>   - A hardware reset GPIO (active low)
> 
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>

Why did you drop Rob's tag?

Cheers,
Conor.

> ---
>  .../devicetree/bindings/iio/imu/adi,adis16607.yaml | 94 ++++++++++++++++++++++
>  MAINTAINERS                                        |  7 ++
>  2 files changed, 101 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml b/Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
> new file mode 100644
> index 000000000000..4545f942365c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
> @@ -0,0 +1,94 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/imu/adi,adis16607.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices ADIS16607 IMU
> +
> +maintainers:
> +  - Radu Sabau <radu.sabau@analog.com>
> +
> +description: |
> +  Analog Devices ADIS16607 Precision MEMS Inertial Measurement Unit (IMU)
> +  with a triaxial gyroscope, triaxial accelerometer, and temperature sensor.
> +  Uses SPI Half-Duplex communication protocol.
> +
> +  The device has five GPIO pins (GPIO0-GPIO4 in the datasheet), each
> +  independently selectable for a single fixed function. This binding
> +  exposes GPIO0's reset function as reset-gpios; GPIO2's sync function
> +  is modeled via the clocks property, and GPIO4's DEV_NOK interrupt via
> +  the interrupts property. GPIO3 (data-ready) is required internally
> +  regardless of board wiring and isn't exposed. GPIO1 (wake) gates
> +  whether the bootloader auto-loads its NVM contents after reset; it
> +  defaults to enabled with a weak pull-up, which already gives normal
> +  operation with no configuration needed, so it isn't exposed either.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - adi,adis16607-2
> +      - adi,adis16607-3
> +
> +  reg:
> +    maxItems: 1
> +
> +  spi-cpha: true
> +
> +  spi-cpol: true
> +
> +  spi-max-frequency:
> +    maximum: 10000000
> +
> +  vdd-supply: true
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  clocks:
> +    description:
> +      Optional external clock for sync mode. If not provided, the internal
> +      8 kHz clock is used.
> +    maxItems: 1
> +
> +  reset-gpios:
> +    description:
> +      Optional GPIO for hardware reset, active low. Routes to the
> +      device's GPIO0 pin, which can be independently set to the reset
> +      function or left idle via an on-chip register.
> +    maxItems: 1
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - spi-cpha
> +  - spi-cpol
> +  - vdd-supply
> +
> +allOf:
> +  - $ref: /schemas/spi/spi-peripheral-props.yaml#
> +
> +unevaluatedProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    #include <dt-bindings/gpio/gpio.h>
> +    spi {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        imu@0 {
> +            compatible = "adi,adis16607-2";
> +            reg = <0>;
> +            spi-cpha;
> +            spi-cpol;
> +            spi-max-frequency = <10000000>;
> +            vdd-supply = <&vdd>;
> +            interrupts = <4 IRQ_TYPE_LEVEL_HIGH>;
> +            interrupt-parent = <&gpio>;
> +            clocks = <&sync_clk>;
> +            reset-gpios = <&gpio 10 GPIO_ACTIVE_LOW>;
> +        };
> +    };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f53047aa8d3c..d5679e8d0ffd 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1792,6 +1792,13 @@ S:	Supported
>  W:	https://ez.analog.com/linux-software-drivers
>  F:	Documentation/devicetree/bindings/iio/imu/adi,adis16550.yaml
>  
> +ANALOG DEVICES INC ADIS16607 DRIVER
> +M:	Radu Sabau <radu.sabau@analog.com>
> +L:	linux-iio@vger.kernel.org
> +S:	Supported
> +W:	https://ez.analog.com/linux-software-drivers
> +F:	Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
> +
>  ANALOG DEVICES INC ADL8113 DRIVER
>  M:	Antoniu Miclaus <antoniu.miclaus@analog.com>
>  L:	linux-iio@vger.kernel.org
> 
> -- 
> 2.43.0
> 
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-11-02 19:36 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-02  9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
2026-11-02  9:25 ` [PATCH v5 1/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
2026-11-02  9:38   ` sashiko-bot
2026-11-02  9:25 ` [PATCH v5 2/8] iio: imu: adis: Add optional self_test callback Radu Sabau via B4 Relay
2026-11-02  9:25 ` [PATCH v5 3/8] iio: imu: adis: Add optional init callback Radu Sabau via B4 Relay
2026-11-02  9:25 ` [PATCH v5 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607 Radu Sabau via B4 Relay
2026-11-02 19:36   ` Conor Dooley
2026-11-02  9:25 ` [PATCH v5 5/8] iio: imu: Add driver for the " Radu Sabau via B4 Relay
2026-11-02  9:25 ` [PATCH v5 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture Radu Sabau via B4 Relay
2026-11-02  9:47   ` sashiko-bot
2026-11-02  9:25 ` [PATCH v5 7/8] iio: imu: adis16607: Add external clock sync support Radu Sabau via B4 Relay
2026-11-02  9:42   ` sashiko-bot
2026-11-02  9:25 ` [PATCH v5 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel Radu Sabau via B4 Relay

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox