mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v4 0/8] iio: imu: Add support for the ADI ADIS16607
@ 2026-10-23  9:30 Radu Sabau via B4 Relay
  2026-10-23  9:30 ` [PATCH v4 1/8] iio: imu: adis: Add optional self_test callback Radu Sabau via B4 Relay
                   ` (7 more replies)
  0 siblings, 8 replies; 10+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-10-23  9:30 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 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 2 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 3 adds an optional post_reset_setup 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).

 - Startup is split across the two new adislib callbacks rather than a
   driver-private initial_startup(): adis16607_post_reset_setup() 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 FIFO-pop command
   and the regular burst-read command are genuinely different
   protocols on this device (different payload shapes, no checksum on
   the FIFO side), so this stays driver-private rather than going
   through adislib's generic burst-read helpers.

 - The FIFO drain in the trigger handler just 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. The datasheet's dual-port FIFO
   architecture means reads and writes don't contend, so there's
   nothing for a disable step to protect against.

 - 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.

 - 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()).

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 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: Add optional self_test callback
      iio: imu: adis: Fix custom reset dispatch in adislib
      iio: imu: adis: Add optional post_reset_setup 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                        | 1325 ++++++++++++++++++++
 include/linux/iio/imu/adis.h                       |   26 +-
 7 files changed, 1477 insertions(+), 5 deletions(-)
---
base-commit: 0fcb05b72344f40806cbe810de8872f1698bf053
change-id: 20260617-adis16607-089ffc655ceb

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



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

* [PATCH v4 1/8] iio: imu: adis: Add optional self_test callback
  2026-10-23  9:30 [PATCH v4 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
@ 2026-10-23  9:30 ` Radu Sabau via B4 Relay
  2026-10-23  9:30 ` [PATCH v4 2/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-10-23  9:30 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.

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 a2bc1d14ed91..388518ebba2e 100644
--- a/drivers/iio/imu/adis.c
+++ b/drivers/iio/imu/adis.c
@@ -360,6 +360,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 bfb6df68e6c9..14f9b304c252 100644
--- a/include/linux/iio/imu/adis.h
+++ b/include/linux/iio/imu/adis.h
@@ -51,6 +51,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
@@ -81,6 +87,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	[flat|nested] 10+ messages in thread

* [PATCH v4 2/8] iio: imu: adis: Fix custom reset dispatch in adislib
  2026-10-23  9:30 [PATCH v4 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
  2026-10-23  9:30 ` [PATCH v4 1/8] iio: imu: adis: Add optional self_test callback Radu Sabau via B4 Relay
@ 2026-10-23  9:30 ` Radu Sabau via B4 Relay
  2026-10-23  9:30 ` [PATCH v4 3/8] iio: imu: adis: Add optional post_reset_setup callback Radu Sabau via B4 Relay
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-10-23  9:30 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")
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 388518ebba2e..5c3bb7259037 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");
@@ -416,9 +413,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 14f9b304c252..c09b381eb619 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>
@@ -178,8 +179,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	[flat|nested] 10+ messages in thread

* [PATCH v4 3/8] iio: imu: adis: Add optional post_reset_setup callback
  2026-10-23  9:30 [PATCH v4 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
  2026-10-23  9:30 ` [PATCH v4 1/8] iio: imu: adis: Add optional self_test callback Radu Sabau via B4 Relay
  2026-10-23  9:30 ` [PATCH v4 2/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
@ 2026-10-23  9:30 ` Radu Sabau via B4 Relay
  2026-10-23  9:30 ` [PATCH v4 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607 Radu Sabau via B4 Relay
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-10-23  9:30 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 post_reset_setup 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..332cc7b0ff35 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->post_reset_setup) {
+		ret = adis->data->post_reset_setup(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..3d7c7febaec6 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
+ * @post_reset_setup: 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 (*post_reset_setup)(struct adis *adis);
 	int (*self_test)(struct adis *adis);
 	const struct adis_timeout *timeouts;
 

-- 
2.43.0



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

* [PATCH v4 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607
  2026-10-23  9:30 [PATCH v4 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
                   ` (2 preceding siblings ...)
  2026-10-23  9:30 ` [PATCH v4 3/8] iio: imu: adis: Add optional post_reset_setup callback Radu Sabau via B4 Relay
@ 2026-10-23  9:30 ` Radu Sabau via B4 Relay
  2026-10-23  9:30 ` [PATCH v4 5/8] iio: imu: Add driver for the " Radu Sabau via B4 Relay
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-10-23  9:30 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)

Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
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	[flat|nested] 10+ messages in thread

* [PATCH v4 5/8] iio: imu: Add driver for the ADI ADIS16607
  2026-10-23  9:30 [PATCH v4 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
                   ` (3 preceding siblings ...)
  2026-10-23  9:30 ` [PATCH v4 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607 Radu Sabau via B4 Relay
@ 2026-10-23  9:30 ` Radu Sabau via B4 Relay
  2026-10-23 10:26   ` Andy Shevchenko
  2026-10-23  9:30 ` [PATCH v4 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture Radu Sabau via B4 Relay
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 10+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-10-23  9:30 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 | 842 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 857 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..2dda306d7b54
--- /dev/null
+++ b/drivers/iio/imu/adis16607.c
@@ -0,0 +1,842 @@
+// 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/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,
+};
+
+/*
+ * SPI Half-Duplex read operation for ADIS16607.
+ * Protocol:
+ *   - First transfer: Send [reg | 0x80, 0, 0, 0] - device latches command
+ *   - Second transfer: Send same command, device responds in bytes 2-3
+ *   - CS toggles between transfers (cs_change = 1 on first xfer)
+ * For 4-byte reads (24-bit sensor data):
+ *   - Read MSW from reg, read LSW from reg+1
+ *   - Combine as: ((val_msw << 8) & 0xFFFF00) | val_lsw
+ */
+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,
+			.bits_per_word = 8,
+			.cs_change = 1,
+		},
+		{
+			.tx_buf = adis->tx,
+			.rx_buf = adis->rx,
+			.len = 4,
+			.bits_per_word = 8,
+		},
+	};
+	unsigned int val_msw, val_lsw;
+	int ret;
+
+	switch (size) {
+	case 2:
+		adis->tx[0] = reg | BIT(7);
+		adis->tx[1] = 0;
+		adis->tx[2] = 0;
+		adis->tx[3] = 0;
+
+		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);
+		adis->tx[1] = 0;
+		adis->tx[2] = 0;
+		adis->tx[3] = 0;
+
+		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 = ((val_msw << 8) & 0xFFFF00) | 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, i;
+
+	/* 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 (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 (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])) {
+			dev_err(&adis->spi->dev,
+				"Self-test failed for axis %d: delta out of range\n", i);
+			ret = -EINVAL;
+			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;
+}
+
+/*
+ * Post-reset bring-up callback for struct adis_data (see adis16607_data
+ * below). __adis_initial_startup() calls this, unlocked, right after it
+ * has handled reset (hardware GPIO or SW reset, adislib's own job) and
+ * before self-test - no mutex is taken here either, matching that
+ * convention. Does the device-specific startup steps adislib has no
+ * concept of: SPI Half-Duplex locking, dev_id verification, and the
+ * bootloader-busy check.
+ */
+static int adis16607_post_reset_setup(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) {
+		dev_err(dev, "failed to lock SPI Half-Duplex mode: %d\n", 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) {
+		dev_err(dev, "device ID mismatch: expected 0x%04x, got 0x%04x\n",
+			adis->data->prod_id, dev_id);
+		return -ENODEV;
+	}
+
+	/* 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) {
+		dev_err(dev, "status check failed: %d\n", 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) {
+		dev_err(dev, "bootloader is busy\n");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+/*
+ * Self-test callback for struct adis_data (see adis16607_data below).
+ * Called by __adis_initial_startup() right after
+ * adis16607_post_reset_setup().
+ */
+static int adis16607_self_test(struct adis *adis)
+{
+	struct adis16607 *st = container_of(adis, struct adis16607, adis);
+	struct device *dev = &adis->spi->dev;
+	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;
+
+	ret = __adis_check_status(adis);
+	if (ret)
+		dev_err(dev, "status check after self-test failed: %d\n", ret);
+
+	return ret;
+}
+
+static int adis16607_firmware_revision_show(struct seq_file *s, void *arg)
+{
+	struct adis16607 *st = s->private;
+	u16 rev;
+	int ret;
+
+	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_REV_ID, &rev);
+	if (ret)
+		return ret;
+
+	seq_printf(s, "%u.%u\n", (rev >> 8) & 0xff, rev & 0xff);
+	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_NUM0, &tmp);
+	if (ret)
+		return ret;
+
+	serial = (u64)tmp;
+
+	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM1, &tmp);
+	if (ret)
+		return ret;
+
+	serial |= ((u64)tmp << 16);
+
+	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM2, &tmp);
+	if (ret)
+		return ret;
+
+	serial |= ((u64)tmp << 32);
+
+	*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 = 0;
+	int ret;
+
+	adis_dev_auto_lock(&st->adis);
+
+	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)
+{
+	u32 dec;
+	int ret;
+
+	if (!freq)
+		return -EINVAL;
+
+	adis_dev_auto_lock(&st->adis);
+
+	dec = DIV_ROUND_CLOSEST(st->clk_freq_mhz, freq);
+	if (dec)
+		dec--;
+
+	dec = min_t(u32, dec, st->info->max_dec);
+
+	ret = __adis_write_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, dec);
+	if (ret)
+		return ret;
+
+	st->sample_period_ns = DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * 1000ULL * (dec + 1),
+						     st->clk_freq_mhz);
+
+	return 0;
+}
+
+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 adis_single_conversion(indio_dev, chan, 0, 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;
+		*val = tmp / 1000;
+		*val2 = (tmp % 1000) * 1000;
+		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:
+		tmp = val * 1000 + val2 / 1000;
+		return adis16607_set_freq(st, tmp);
+
+	default:
+		return -EINVAL;
+	}
+}
+
+#define ADIS16607_MOD_CHAN(_type, _mod, _address, _si) { \
+	.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 = (_address), \
+	.scan_index = (_si), \
+	.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_REG_ ## _mod ## _ACCEL, \
+			   ADIS16607_SCAN_ACCEL_ ## _mod)
+
+#define ADIS16607_GYRO_CHANNEL(_mod) \
+	ADIS16607_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_ ## _mod, \
+			   ADIS16607_REG_ ## _mod ## _GYRO, \
+			   ADIS16607_SCAN_GYRO_ ## _mod)
+
+#define ADIS16607_MOD_CHAN_DELTA(_type, _mod, _address, _si) { \
+	.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 = (_address), \
+	.scan_index = (_si), \
+	.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_REG_ ## _mod ## _DELTANG, \
+				 ADIS16607_SCAN_DELTANG_ ## _mod)
+
+#define ADIS16607_DELTVEL_CHAN(_mod) \
+	ADIS16607_MOD_CHAN_DELTA(IIO_DELTA_VELOCITY, IIO_MOD_ ## _mod, \
+				 ADIS16607_REG_ ## _mod ## _DELTVEL, \
+				 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_REG_TEMP,
+		.scan_index = ADIS16607_SCAN_TEMP,
+		.scan_type = {
+			.sign = 's',
+			.realbits = 16,
+			.storagebits = 16,
+			.endianness = IIO_BE,
+		},
+	},
+	IIO_CHAN_SOFT_TIMESTAMP(13),
+};
+
+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,
+	.post_reset_setup = adis16607_post_reset_setup,
+	.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)
+{
+	st->clk_freq_mhz = st->info->int_clk_hz * MILLI;
+	st->sample_period_ns =
+		DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * MILLI, 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 IMU driver");
+MODULE_IMPORT_NS("IIO_ADISLIB");
+MODULE_LICENSE("GPL");

-- 
2.43.0



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

* [PATCH v4 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture
  2026-10-23  9:30 [PATCH v4 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
                   ` (4 preceding siblings ...)
  2026-10-23  9:30 ` [PATCH v4 5/8] iio: imu: Add driver for the " Radu Sabau via B4 Relay
@ 2026-10-23  9:30 ` Radu Sabau via B4 Relay
  2026-10-23  9:30 ` [PATCH v4 7/8] iio: imu: adis16607: Add external clock sync support Radu Sabau via B4 Relay
  2026-10-23  9:30 ` [PATCH v4 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel Radu Sabau via B4 Relay
  7 siblings, 0 replies; 10+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-10-23  9:30 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 | 349 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 346 insertions(+), 3 deletions(-)

diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
index 2dda306d7b54..b1f3667d8ce3 100644
--- a/drivers/iio/imu/adis16607.c
+++ b/drivers/iio/imu/adis16607.c
@@ -12,6 +12,9 @@
 #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/math.h>
 #include <linux/minmax.h>
 #include <linux/module.h>
@@ -42,8 +45,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
@@ -54,8 +60,26 @@
 
 /* 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
+
+/* Burst read - Half-Duplex mode (DATA_CNTR enabled) */
+#define ADIS16607_BURST_CMD_SIZE	4
+
 /* Self-test registers and masks */
 #define ADIS16607_REG_SELF_TEST_DATA(x)	((x) + 0x23)
 #define ADIS16607_SNSR_SELF_TEST_MASK	BIT(6)
@@ -86,6 +110,11 @@ struct adis16607 {
 	const struct adis16607_chip_info *info;
 	unsigned long clk_freq_mhz;
 	u64 sample_period_ns;
+	u16 fifo_watermark;
+	/* Scratch buffer used for FIFO pop operations, see adis16607_fifo_pop() */
+	u8 fifo_buf[ADIS16607_BURST_CMD_SIZE + ADIS16607_FIFO_DATA_LEN] __aligned(IIO_DMA_MINALIGN);
+	/* IIO buffer for reordered data - sized for max channels */
+	u8 data[64] __aligned(IIO_DMA_MINALIGN);
 };
 
 enum {
@@ -215,6 +244,286 @@ 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_BURST_CMD_SIZE + ADIS16607_FIFO_DATA_LEN,
+		.bits_per_word = 8,
+	};
+	int ret;
+
+	/* FIFO data command */
+	st->fifo_buf[0] = ADIS16607_FIFO_DATA_CMD;
+	st->fifo_buf[1] = 0;
+	st->fifo_buf[2] = 0;
+	st->fifo_buf[3] = 0;
+
+	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_BURST_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));
+
+	for_each_set_bit(bit, indio_dev->active_scan_mask, indio_dev->masklength) {
+		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_timestamp(indio_dev, st->data, timestamp);
+}
+
+/*
+ * Trigger handler - reads samples from 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 irqreturn_t adis16607_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	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, i;
+
+	mutex_lock(&st->adis.state_lock);
+
+	/* Check if FIFO threshold is met */
+	ret = __adis_read_reg_16(&st->adis, ADIS16607_REG_DIAG_STAT, &diag_stat);
+	if (ret)
+		goto unlock;
+
+	if (!(diag_stat & ADIS16607_FIFO_THR_MET_MASK))
+		goto unlock;
+
+	/* Read FIFO word count */
+	ret = __adis_read_reg_16(&st->adis, ADIS16607_REG_FIFO_WORD_CNT, &word_cnt);
+	if (ret)
+		goto unlock;
+
+	/* Calculate number of complete samples */
+	sample_cnt = word_cnt / ADIS16607_FIFO_WORDS_PER_SAMPLE;
+	if (!sample_cnt)
+		goto unlock;
+
+	/*
+	 * pf->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 pf->timestamp
+	 * for the newest sample, instead of projecting past it.
+	 */
+	timestamp = pf->timestamp - (s64)(sample_cnt - 1) * st->sample_period_ns;
+
+	/* Pop and process each sample */
+	for (i = 0; i < sample_cnt; i++) {
+		ret = adis16607_fifo_pop(st, fifo_sample);
+		if (ret)
+			goto unlock;
+
+		adis16607_fifo_push_sample(indio_dev, fifo_sample, timestamp);
+		timestamp += st->sample_period_ns;
+	}
+
+unlock:
+	mutex_unlock(&st->adis.state_lock);
+	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;
+
+	adis_dev_auto_lock(adis);
+
+	/* Disable FIFO */
+	return __adis_write_reg_16(adis, ADIS16607_REG_USER_FIFO_CFG, 0);
+}
+
+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.
  */
@@ -306,8 +615,9 @@ static int adis16607_sensor_self_test(struct adis16607 *st)
  * has handled reset (hardware GPIO or SW reset, adislib's own job) and
  * before self-test - no mutex is taken here either, matching that
  * convention. Does the device-specific startup steps adislib has no
- * concept of: SPI Half-Duplex locking, dev_id verification, and the
- * bootloader-busy check.
+ * concept of: SPI Half-Duplex locking, dev_id verification, the
+ * bootloader-busy check, enabling 32-bit burst mode + DATA_CNTR, and
+ * the DR/DEV_NOK pin routing.
  */
 static int adis16607_post_reset_setup(struct adis *adis)
 {
@@ -355,7 +665,29 @@ static int adis16607_post_reset_setup(struct adis *adis)
 		return -EBUSY;
 	}
 
-	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);
 }
 
 /*
@@ -669,6 +1001,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[] = {
@@ -690,6 +1023,7 @@ static const struct adis_data adis16607_data = {
 	.prod_id_reg = ADIS16607_REG_DEV_ID,
 	.prod_id = 0x6000,
 	.unmasked_drdy = true,
+	.has_fifo = true,
 	.post_reset_setup = adis16607_post_reset_setup,
 	.self_test = adis16607_self_test,
 	.status_error_msgs = adis16607_status_error_msgs,
@@ -765,6 +1099,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");
@@ -803,6 +1139,13 @@ static int adis16607_probe(struct spi_device *spi)
 	if (ret)
 		return ret;
 
+	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	[flat|nested] 10+ messages in thread

* [PATCH v4 7/8] iio: imu: adis16607: Add external clock sync support
  2026-10-23  9:30 [PATCH v4 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
                   ` (5 preceding siblings ...)
  2026-10-23  9:30 ` [PATCH v4 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture Radu Sabau via B4 Relay
@ 2026-10-23  9:30 ` Radu Sabau via B4 Relay
  2026-10-23  9:30 ` [PATCH v4 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel Radu Sabau via B4 Relay
  7 siblings, 0 replies; 10+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-10-23  9:30 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 and SYNC_SCALED modes depending on the
supplied frequency, with the internal 8 kHz clock used as the default
when no clock is provided.

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

diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
index b1f3667d8ce3..3c5b59820be1 100644
--- a/drivers/iio/imu/adis16607.c
+++ b/drivers/iio/imu/adis16607.c
@@ -7,6 +7,7 @@
 #include <linux/array_size.h>
 #include <linux/bitfield.h>
 #include <linux/bitops.h>
+#include <linux/clk.h>
 #include <linux/debugfs.h>
 #include <linux/delay.h>
 #include <linux/iio/buffer.h>
@@ -48,6 +49,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
@@ -60,10 +62,12 @@
 
 /* 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)
 
 /* FIFO masks */
@@ -90,8 +94,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;
@@ -103,6 +121,7 @@ struct adis16607_chip_info {
 	u32 deltvel_max_val;
 	u32 int_clk_hz;
 	u16 max_dec;
+	u8 num_sync;
 };
 
 struct adis16607 {
@@ -110,6 +129,7 @@ struct adis16607 {
 	const struct adis16607_chip_info *info;
 	unsigned long clk_freq_mhz;
 	u64 sample_period_ns;
+	u32 sync_mode;
 	u16 fifo_watermark;
 	/* Scratch buffer used for FIFO pop operations, see adis16607_fifo_pop() */
 	u8 fifo_buf[ADIS16607_BURST_CMD_SIZE + ADIS16607_FIFO_DATA_LEN] __aligned(IIO_DMA_MINALIGN);
@@ -1004,6 +1024,12 @@ static const struct iio_info adis16607_info = {
 	.hwfifo_set_watermark = adis16607_set_watermark,
 };
 
+static const struct adis16607_sync adis16607_sync_modes[] = {
+	{ ADIS16607_SYNC_DEFAULT, 0, 0 },
+	{ ADIS16607_SYNC_DIRECT, 401, 8000 },
+	{ ADIS16607_SYNC_SCALED, 1, 400 },
+};
+
 static const char * const adis16607_status_error_msgs[] = {
 	[9] = "Boot Memory Failure",
 	[11] = "Power Supply Failure",
@@ -1048,6 +1074,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 = {
@@ -1063,17 +1091,96 @@ 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)
 {
-	st->clk_freq_mhz = st->info->int_clk_hz * MILLI;
-	st->sample_period_ns =
-		DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * MILLI, st->clk_freq_mhz);
+	struct device *dev = &st->adis.spi->dev;
+	const struct adis16607_sync *sync_mode_data = NULL;
+	unsigned long ext_clk_hz = 0;
+	u16 sync_scale = 0;
+	u16 dec;
+	struct clk *clk;
+	int ret, i;
+
+	clk = devm_clk_get_optional_enabled(dev, NULL);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	if (clk) {
+		ext_clk_hz = clk_get_rate(clk);
+
+		for (i = 1; i < st->info->num_sync; i++) {
+			if (ext_clk_hz >= st->info->sync_mode[i].min_rate &&
+			    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",
+					     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;
+
+	if (st->sync_mode == ADIS16607_SYNC_SCALED)
+		sync_scale = DIV_ROUND_CLOSEST(st->info->int_clk_hz, ext_clk_hz);
+
+	ret = adis_write_reg_16(&st->adis, ADIS16607_REG_USER_SYNC,
+				(st->sync_mode == ADIS16607_SYNC_SCALED) ?
+				ADIS16607_SYNC_MODE_MASK | sync_scale : 0);
+	if (ret)
+		return ret;
+
+	switch (st->sync_mode) {
+	case ADIS16607_SYNC_SCALED:
+		/*
+		 * sync_scale is a rounded integer, so the hardware's actual
+		 * effective rate is ext_clk_hz * 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 = (unsigned long)ext_clk_hz * sync_scale * 1000;
+		break;
+	case ADIS16607_SYNC_DIRECT:
+		/* DIRECT: external clock is the effective internal rate */
+		st->clk_freq_mhz = ext_clk_hz * 1000;
+		break;
+	default:
+		st->clk_freq_mhz = (unsigned long)st->info->int_clk_hz * 1000;
+		break;
+	}
+
+	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, &dec);
+	if (ret)
+		return ret;
+
+	st->sample_period_ns = DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * 1000ULL * (dec + 1),
+						     st->clk_freq_mhz);
+
 	return 0;
 }
 

-- 
2.43.0



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

* [PATCH v4 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel
  2026-10-23  9:30 [PATCH v4 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
                   ` (6 preceding siblings ...)
  2026-10-23  9:30 ` [PATCH v4 7/8] iio: imu: adis16607: Add external clock sync support Radu Sabau via B4 Relay
@ 2026-10-23  9:30 ` Radu Sabau via B4 Relay
  7 siblings, 0 replies; 10+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-10-23  9:30 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 | 35 ++++++++++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
index 3c5b59820be1..f73df022e819 100644
--- a/drivers/iio/imu/adis16607.c
+++ b/drivers/iio/imu/adis16607.c
@@ -55,6 +55,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 */
@@ -153,6 +159,15 @@ enum {
 	ADIS16607_SCAN_TEMP,
 };
 
+static const u32 adis16607_calib_bias[] = {
+	[ADIS16607_SCAN_ACCEL_X] = ADIS16607_REG_XA_BIAS,
+	[ADIS16607_SCAN_ACCEL_Y] = ADIS16607_REG_YA_BIAS,
+	[ADIS16607_SCAN_ACCEL_Z] = ADIS16607_REG_ZA_BIAS,
+	[ADIS16607_SCAN_GYRO_X] = ADIS16607_REG_XG_BIAS,
+	[ADIS16607_SCAN_GYRO_Y] = ADIS16607_REG_YG_BIAS,
+	[ADIS16607_SCAN_GYRO_Z] = ADIS16607_REG_ZG_BIAS,
+};
+
 /*
  * SPI Half-Duplex read operation for ADIS16607.
  * Protocol:
@@ -863,6 +878,7 @@ static int adis16607_read_raw(struct iio_dev *indio_dev,
 			      int *val, int *val2, long info)
 {
 	struct adis16607 *st = iio_priv(indio_dev);
+	const int idx = chan->scan_index;
 	int ret;
 	u32 tmp;
 
@@ -900,6 +916,16 @@ static int adis16607_read_raw(struct iio_dev *indio_dev,
 		*val = 5000;
 		return IIO_VAL_INT;
 
+	case IIO_CHAN_INFO_CALIBBIAS: {
+		u16 tmp16;
+
+		ret = adis_read_reg_16(&st->adis, adis16607_calib_bias[idx], &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)
@@ -918,6 +944,7 @@ static int adis16607_write_raw(struct iio_dev *indio_dev,
 			       int val, int val2, long info)
 {
 	struct adis16607 *st = iio_priv(indio_dev);
+	const int idx = chan->scan_index;
 	u32 tmp;
 
 	switch (info) {
@@ -925,6 +952,11 @@ static int adis16607_write_raw(struct iio_dev *indio_dev,
 		tmp = val * 1000 + val2 / 1000;
 		return adis16607_set_freq(st, tmp);
 
+	case IIO_CHAN_INFO_CALIBBIAS:
+		if (val < S16_MIN || val > S16_MAX)
+			return -EINVAL;
+		return adis_write_reg_16(&st->adis, adis16607_calib_bias[idx], val);
+
 	default:
 		return -EINVAL;
 	}
@@ -934,7 +966,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 = (_address), \

-- 
2.43.0



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

* Re: [PATCH v4 5/8] iio: imu: Add driver for the ADI ADIS16607
  2026-10-23  9:30 ` [PATCH v4 5/8] iio: imu: Add driver for the " Radu Sabau via B4 Relay
@ 2026-10-23 10:26   ` Andy Shevchenko
  0 siblings, 0 replies; 10+ messages in thread
From: Andy Shevchenko @ 2026-10-23 10:26 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

On Fri, Oct 23, 2026 at 12:30:22PM +0300, Radu Sabau via B4 Relay wrote:

> 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.

...

> +++ b/drivers/iio/imu/Kconfig

> +	tristate "Analog Devices ADIS16607 and similar IMU driver"

(see below)


...

> +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;

_Hz

> +	u16 max_dec;
> +};

...

> +struct adis16607 {
> +	struct adis adis;
> +	const struct adis16607_chip_info *info;
> +	unsigned long clk_freq_mhz;

_MHz or _mHz? (I believe it's the first one.)
This is why case-sensitive units (as per physics) are important.

> +	u64 sample_period_ns;
> +};

...

> +		/* Combine: 24-bit value with MSW in upper 16 bits, LSW in lower 8 bits */

Make it multi-line and to fix 80 characters.

> +		*val = ((val_msw << 8) & 0xFFFF00) | val_lsw;

Why not GENMASK()? Can val_msw be u16 or other 16-bit type?

...

> +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, i;

Why is 'i' signed?

> +	/* 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 (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 (i = 0; i < ARRAY_SIZE(st2_val); i++) {

In both for-loops iterator is local, so

	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])) {
> +			dev_err(&adis->spi->dev,
> +				"Self-test failed for axis %d: delta out of range\n", i);
> +			ret = -EINVAL;
> +			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;
> +}

...

> +static int adis16607_self_test(struct adis *adis)
> +{
> +	struct adis16607 *st = container_of(adis, struct adis16607, adis);
> +	struct device *dev = &adis->spi->dev;
> +	u16 diag_stat;
> +	int ret;
> +
> +	/* Inertial Settling Time (Table 1), needed before self-test reads sensor data */

Make it fit 80.

> +	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;
> +
> +	ret = __adis_check_status(adis);
> +	if (ret)
> +		dev_err(dev, "status check after self-test failed: %d\n", ret);
> +
> +	return ret;
> +}

...

> +static int adis16607_firmware_revision_show(struct seq_file *s, void *arg)
> +{
> +	struct adis16607 *st = s->private;
> +	u16 rev;

Can this be

	struct revision {
		u8 minor;
		u8 major;
	} rev;

?

> +	int ret;
> +
> +	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_REV_ID, &rev);
> +	if (ret)
> +		return ret;
> +
> +	seq_printf(s, "%u.%u\n", (rev >> 8) & 0xff, rev & 0xff);
> +	return 0;
> +}

...

> +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_NUM0, &tmp);
> +	if (ret)
> +		return ret;
> +
> +	serial = (u64)tmp;

How is explicit casting helpful here?

> +	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM1, &tmp);
> +	if (ret)
> +		return ret;
> +
> +	serial |= ((u64)tmp << 16);
> +
> +	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM2, &tmp);
> +	if (ret)
> +		return ret;
> +
> +	serial |= ((u64)tmp << 32);

What about reversing the order and shift serial instead?

	u64 serial = 0;

	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;
> +}

...

> +static int adis16607_set_freq(struct adis16607 *st, u32 freq)
> +{
> +	u32 dec;
> +	int ret;
> +
> +	if (!freq)
> +		return -EINVAL;
> +
> +	adis_dev_auto_lock(&st->adis);
> +
> +	dec = DIV_ROUND_CLOSEST(st->clk_freq_mhz, freq);
> +	if (dec)
> +		dec--;
> +
> +	dec = min_t(u32, dec, st->info->max_dec);

No min_t(). Make the types to be compatible for min().

> +	ret = __adis_write_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, dec);
> +	if (ret)
> +		return ret;
> +
> +	st->sample_period_ns = DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * 1000ULL * (dec + 1),
> +						     st->clk_freq_mhz);

Make it fit. Using temporary variable usually helps. Also you don't need (u64)
casting or ULL suffix. On top of that, what is another 1000 multiplier for?

I would expect a comment on top with explaining magic(s).

> +	return 0;
> +}

...

> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		ret = adis16607_get_freq(st, &tmp);
> +		if (ret)
> +			return ret;
> +		*val = tmp / 1000;
> +		*val2 = (tmp % 1000) * 1000;

What is 1000? HZ_PER_KHZ? KHZ_PER_MHZ?

> +		return IIO_VAL_INT_PLUS_MICRO;

...

> +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:
> +		tmp = val * 1000 + val2 / 1000;
> +		return adis16607_set_freq(st, tmp);

Ditto.

> +	default:
> +		return -EINVAL;
> +	}
> +}

...

> +#define ADIS16607_MOD_CHAN(_type, _mod, _address, _si) { \
> +	.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 = (_address), \
> +	.scan_index = (_si), \
> +	.scan_type = { \
> +		.sign = 's', \
> +		.realbits = 24, \
> +		.storagebits = 32, \
> +		.endianness = IIO_BE, \
> +	}, \
> +}

This is not a function, I would rather shift it right by a tab with a split
(also indent the backslashes, it makes it easier to read).

#define ADIS16607_MOD_CHAN(_type, _mod, _address, _si)		\
	{							\
		.type = (_type),				\
		...
	}

...

> +#define ADIS16607_MOD_CHAN_DELTA(_type, _mod, _address, _si) { \
> +	.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 = (_address), \
> +	.scan_index = (_si), \
> +	.scan_type = { \
> +		.sign = 's', \
> +		.realbits = 24, \
> +		.storagebits = 32, \
> +		.endianness = IIO_BE, \
> +	}, \
> +}

Ditto.

...

> +/*
> + * 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)
> +{
> +	st->clk_freq_mhz = st->info->int_clk_hz * MILLI;

So, it's milli-hertz then? We have MILLIHZ_PER_HZ.

> +	st->sample_period_ns =
> +		DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * MILLI, st->clk_freq_mhz);
> +	return 0;
> +}

Yeah, this magic 1000 should be explained everywhere.

...

> +MODULE_DESCRIPTION("Analog Devices ADIS16607 IMU driver");

Make the text the same as per Kconfig. Whatever wording you choose.

-- 
With Best Regards,
Andy Shevchenko



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

end of thread, other threads:[~2026-10-23 10:26 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-23  9:30 [PATCH v4 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 1/8] iio: imu: adis: Add optional self_test callback Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 2/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 3/8] iio: imu: adis: Add optional post_reset_setup callback Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607 Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 5/8] iio: imu: Add driver for the " Radu Sabau via B4 Relay
2026-10-23 10:26   ` Andy Shevchenko
2026-10-23  9:30 ` [PATCH v4 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 7/8] iio: imu: adis16607: Add external clock sync support Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 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

all inboxes | Powered by JetHome®