* [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607
@ 2026-11-02 9:25 Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 1/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02 9:25 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron
This series adds support for the Analog Devices ADIS16607, a precision
MEMS IMU with triaxial gyroscope, triaxial accelerometer, and
temperature sensor, communicating over a non-standard SPI Half-Duplex
protocol.
Patch 1 fixes the reset dispatch: both __adis_initial_startup()'s reset
fallback and the public adis_reset() wrapper were calling
__adis_reset() directly instead of dispatching through
adis->ops->reset(). That bug is invisible for every existing adislib
driver (adis_default_ops.reset is __adis_reset() itself), but silently
breaks a driver with fully custom ops - like this one - on any board
without a reset-gpios property, or any caller of adis_reset() on a
device like adis16550 whose custom reset op needs a different write
size than __adis_reset()'s hardcoded 8-bit one. Carries a Fixes tag for
the commit that introduced the hardcoded call; no stable backport is
needed since the bug only manifests for a custom-ops driver, and this
is the first one.
Patch 2 adds an optional self_test callback to adislib, for devices
whose self-test sequence doesn't fit the generic
mask-write/sleep/check-status pattern.
Patch 3 adds an optional init callback to adislib, for devices that
need extra device-specific bring-up between reset and self-test (SPI
mode locking, status/bootloader checks, pin or burst-mode
configuration, etc.) - not all ADIS devices need this, so it's only
called when set, same as self_test.
Patch 4 adds the devicetree binding.
Patches 5-8 add the driver in functional groupings, as requested in
v1 review:
- Patch 5: base driver - probe, raw channel reads, decimation/sample
frequency, startup and self-test via adislib, debugfs identity
entries. No buffered capture yet.
- Patch 6: buffered capture via the device's hardware FIFO.
- Patch 7: external clock sync support.
- Patch 8: calibration bias support for gyro/accel.
Notable points for reviewers:
- The device does not implement the standard ADIS SPI protocol used
by other parts in the family; reads/writes are done over a custom
Half-Duplex sequence (see adis16607_spi_read()/adis16607_spi_write()
and the adis_ops overrides). Half-Duplex is also what gives access
to the device's burst read, which pops or reads a whole set of
samples in a single CS-held transfer.
- Startup is split across the two new adislib callbacks rather than a
driver-private initial_startup(): adis16607_init() does SPI
Half-Duplex locking, dev_id verification, the bootloader-busy
check, and (once FIFO is wired up in patch 6) burst-mode and
DR/DEV_NOK interrupt-pin configuration; adis16607_self_test() does
the actual per-axis delta-check, clearing the SELF_TEST register on
every exit path (not just success) so a failure doesn't leave the
sensor sitting in its self-test/force state.
- GPIO0's reset-pin routing (RESET_GPIO_MASK in USER_GPIO_CFG) is
runtime-writable on this part, so a prior write (bug, firmware,
direct register access) could leave it de-routed, silently
defeating reset-gpios. Patch 5's probe() forces it back to the
reset function over SPI before relying on toggling the pin -
warning rather than erroring out on failure, so a still-responsive
device still gets a real chance at reset. Patch 6 adds the
DR/DEV_NOK pins to the same USER_GPIO_CFG register via a
read-modify-write, so it can't clobber what probe() already set.
- The hardware FIFO is the *only* buffered capture path - there is no
single-sample direct burst-read trigger mode. The device offers its
burst read on two registers: DIAG_STAT returns a single set of the
most recent samples directly from the sensor, bypassing the FIFO,
while FIFO_DATA pops one set from the FIFO. They are genuinely
different commands with different payload shapes, so the FIFO pop
stays driver-private rather than going through adislib's generic
burst-read helpers.
- The FIFO drain in the trigger handler reads DIAG_STAT (which
clears FIFO_THRESH_MET as a side effect) and pops however many
complete samples are available - no disabling, flushing, or
re-enabling around the drain, since the datasheet's dual-port FIFO
architecture means reads and writes don't contend. The FIFO is
flushed and DIAG_STAT cleared once, when the buffer is disabled, so
that the next session starts clean.
- The FIFO-threshold interrupt is signaled via DEV_NOK on the
device's one physically-wired interrupt pin, which stays asserted
(level-triggered) until DIAG_STAT is read and cleared - this sets
has_fifo on adis_data and requests the IRQ through adislib's
threaded/level path, matching adislib's actual contract for that
flag. probe() rejects any other interrupt type.
- An optional external clock (clocks property) selects between
SYNC_DIRECT and SYNC_SCALED modes depending on the supplied
frequency, with the internal 8 kHz clock used as the default when
no clock is provided (adis16607_config_clk()). The clock rate is
requested as exclusive. In scaled mode the sync scale is picked
when sampling_frequency is set; as that and the decimation can't
change while capturing, such writes return -EBUSY with the buffer
enabled.
This has been tested on a Raspberry Pi 4 (arm64) with the device wired
over SPI, exercising the FIFO buffered-capture path with the updated
DEV_NOK/level-triggered interrupt wiring.
To: Lars-Peter Clausen <lars@metafoo.de>
To: Michael Hennerich <Michael.Hennerich@analog.com>
To: Jonathan Cameron <jic23@kernel.org>
To: David Lechner <dlechner@baylibre.com>
To: Nuno Sá <nuno.sa@analog.com>
To: Andy Shevchenko <andy@kernel.org>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Alexandru Ardelean <alexandru.ardelean@analog.com>
Cc: linux-iio@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
Changes in v5:
- Reordered the adislib patches so the reset-dispatch fix comes first
and the self_test callback second (Nuno).
- Renamed the post_reset_setup callback to init (Jonathan).
- Dropped Rob Herring's Reviewed-by tag on the dt-bindings patch - the
binding's GPIO-handling description changed substantially since v3
(Jonathan).
- Base driver: unit-aware names (int_clk_Hz, clk_freq_mHz) and
MILLIHZ_PER_HZ/MICROHZ_PER_HZ instead of bare 1000s; the sample
period is now computed through a u64 temporary, so it stays correct
on 32-bit; GENMASK()/FIELD_PREP()/FIELD_GET() in the register
accessors, with a firmware revision struct and a shift-accumulated
serial number; unsigned loop counters scoped to their loops;
MODULE_DESCRIPTION() matching Kconfig; reworked channel macro
indentation and wrapped over-long comments (Andy).
- Base driver: dev_err_probe() for probe-only errors, dropped the log
lines duplicating what __adis_check_status() already prints, min()
instead of min_t(), removed stray blank lines in read_raw(), dropped
the protocol comment above adis16607_spi_read() and the redundant
bits_per_word/tx zeroing, get_freq() uses the locked helper and
set_freq() takes the lock only around the register writes (Nuno).
- Base driver: a negative sampling_frequency is now rejected instead
of wrapping around (Sashiko).
- Base driver: channel .address is now an index into a per-channel
register table instead of a raw register number, with a
static_assert() against the channel enum; the calibration bias patch
extends the same table instead of indexing by scan_index (Jonathan).
- Base driver: DEC_RATE is reset to 0 before a new value is written,
as the datasheet recommends.
- FIFO: iio_for_each_active_channel() instead of reading the mask
directly (Nuno, Jonathan, Sashiko); the scan buffer is sized with
IIO_DECLARE_BUFFER_WITH_TS() and pushed with
iio_push_to_buffers_with_ts() (Jonathan); the trigger handler uses
the locking helper (Nuno).
- FIFO: the interrupt type is validated at probe and irq_flag is set
to level-high, which was never set before (Sashiko); the FIFO is
flushed and DIAG_STAT cleared when the buffer is disabled, so a
stale session can't leak into the next (Sashiko).
- FIFO: fixed the FIFO_DATA transfer, whose header is 2 bytes and not
4; samples were read 2 bytes late. Writing sampling_frequency while
the buffer is enabled now returns -EBUSY.
- Sync: requested an exclusive clock rate instead of a notifier
(Nuno), which also covers a controllable clock (Jonathan); the sync
scale is now picked per requested sampling frequency (Nuno).
- Sync: corrected the accepted external clock ranges to the datasheet
(direct 1 kHz to 8 kHz, scaled up to 400 Hz); the initial scale now
keeps the multiplied clock at or below the internal rate; loss of
sync or PLL lock is warned about from the trigger handler. Reverse
xmas tree declarations and a loop-scoped unsigned counter
(Jonathan).
- Link to v4: https://lore.kernel.org/r/20261023-adis16607-v4-0-a4d541ebfd13@analog.com
Changes in v4:
- Split the combined adislib commit into three: self_test callback,
the reset-dispatch fix, and post_reset_setup callback, each its own
patch - the original bundled all three under one "Also..." (Jonathan).
- Simplified the reset-dispatch fix: removed the internal settle-time
sleep from __adis_reset() entirely and made both call sites
(__adis_initial_startup(), adis_reset()) sleep unconditionally after
dispatching through adis->ops->reset() - no more comparing against
__adis_reset() to decide whether to sleep (Jonathan).
- dt-bindings: commit message now states how adis16607-2/-3 actually
differ (gyro full-scale range only) instead of just asserting two
variants exist (Jonathan). Narrowed reset-gpios's description to
just itself, and expanded the top-level description to explain the
device's five GPIO pins and where each one's function is actually
modeled (clocks, interrupts, or not exposed) (Jonathan).
- Base driver: moved the RESET_GPIO_MASK force-write from after reset
to before it, in probe() - a previously-defeated reset-pin routing
(buggy driver, direct reg access) now gets a chance to be corrected
before relying on the GPIO toggle, instead of too late to matter;
only warns rather than erroring out on failure, so a still-live
device keeps its chance at a real reset (Jonathan). Removed the
now-redundant duplicate of that same write from
adis16607_post_reset_setup() (Andy).
- Base driver: large style/cleanup batch - dropped an unneeded
<linux/kernel.h> include, reordered struct adis16607 so struct adis
is first (cheaper container_of(), measured -0.41% code size via
bloat-o-meter), use spi_message_init_with_transfers() instead of
repeated spi_message_add_tail(), ARRAY_SIZE() instead of hardcoded
loop bounds, put_unaligned_be16(), combined the firmware/revision
debugfs files into one "major.minor"-formatted entry, dropped an
unnecessary #ifdef CONFIG_DEBUG_FS wrapper, guard()/
adis_dev_auto_lock() instead of manual mutex lock/unlock, -ENODATA
instead of -EINVAL for an unmatched chip_info, unit-suffixed
clk_freq_mhz/int_clk_hz field names with MILLI from <linux/units.h>
instead of a bare magic 1000, de-duplicated/inlined several
single- or two-use macros (Andy, Jonathan).
- Base driver: the self-test startup delay now cites the actual
datasheet value (Inertial Settling Time, 40ms) instead of an
unexplained 100ms that didn't match anything in the datasheet
(Andy). Self-test now clears the SELF_TEST register on every
failure path, not just on success (Sashiko AI).
- FIFO: dropped disabling the FIFO before draining and the
flush-and-re-enable afterward entirely - the datasheet confirms
overflow-only overwrite (not overwrite-during-read) and a dual-port
architecture with no read/write contention, so there was nothing
for the disable step to protect against, and it was costing real
samples during its own disabled window (Jonathan). fifo_buf is now
a fixed-size array instead of a heap allocation reallocated on
every scan-mode change; deleted the now-unnecessary
update_scan_mode() and its devm cleanup entirely (Jonathan).
guard()/adis_dev_auto_lock() and direct returns in buffer
postenable/postdisable (Jonathan). clamp() instead of clamp_t() -
confirmed by building that the forced cast wasn't needed (Jonathan).
- Sync: adis16607_config_clk() now reads DEC_RATE live instead of
assuming it's 0 after reset, the same way adis16607_get_freq()
already does - consistent with how USER_SYNC is already written
explicitly rather than relying on its own reset default (Jonathan).
- Link to v3: https://lore.kernel.org/r/20261015-adis16607-v3-0-f972d4efee85@a
Changes in v3:
- Fixed the public adis_reset() wrapper (adis.h) to also dispatch
through adis->ops->reset() instead of hardcoding __adis_reset() -
the same bug class as the v2 __adis_initial_startup() fix, just a
second call site that was missed. Switched both checks from
comparing against adis_default_ops to adis->ops->reset != __adis_reset,
which also works from the header, where adis_default_ops isn't
visible (Sashiko).
- Fixed dt-binding wording: dropped "compatible" from the variant
description (reads as a DT compatible-string fallback claim, which
doesn't exist here - the two variants aren't interchangeable) and
dropped a Linux-driver-specific sentence from reset-gpios's
description (Conor); removed a stale commit-message bullet claiming
a FIFO watermark property that was never in the binding (Sashiko).
- Dropped a redundant <linux/mod_devicetable.h> include -
<linux/spi/spi.h> already provides it (Uwe, Andy); switched
adis16607_id[] to named initializers (Uwe).
- Fixed FIFO sample timestamps: they were projected forward from the
IRQ-fire time instead of back-dated, so the newest sample in a batch
ended up timestamped in the future. Now back-dated so the newest
sample lands on the IRQ-fire time and earlier ones count back from
it (Sashiko).
- Added a comment explaining why disabling the FIFO during drain, and
the flush afterward, don't lose in-flight samples - no new samples
are enqueued at all while disabled, so nothing can arrive in that
window for the flush to drop (Sashiko asked twice, independently).
- Reworked external clock sync configuration (adis16607_config_clk())
so every mode (default/direct/scaled) explicitly writes its own
complete SYNC_GPIO_MASK/USER_SYNC state, instead of only writing
conditionally when enabling non-default sync (Sashiko x2); switched
the sync scale factor from truncating to rounding division (Sashiko);
fixed a real bug where SCALED mode's effective clock was hardcoded
to the nominal internal rate instead of the actual achieved rate
(ext_clk_hz * sync_scale), which would have caused FIFO timestamps
to drift whenever the external clock didn't divide the internal
clock exactly (Sashiko).
- Added range validation before writing calibration bias - out-of-
range values were silently truncated to 16 bits instead of being
rejected (Sashiko).
- Link to v2: https://lore.kernel.org/r/20261008-adis16607-v2-0-7c94df80878a@analog.com
Changes in v2:
- Split the driver commit into functional groupings per review:
base, FIFO, external clock sync, calibration bias (Nuno).
- Added adis_data.self_test and adis_data.post_reset_setup callbacks
to adislib instead of duplicating startup/self-test logic in the
driver (Nuno). post_reset_setup is a second, honestly-named hook
added after review feedback that overloading self_test to also mean
"everything else the device needs at startup" was misleading.
- Fixed __adis_initial_startup()'s reset fallback to dispatch through
adis->ops->reset() instead of a hardcoded __adis_reset() call -
found while wiring up this driver's custom reset op. Added a Fixes
tag for the originating commit.
- Removed the direct single-sample burst-read capture path entirely;
the hardware FIFO is now the only buffered capture mode. Dropped
adi,fifo-enable from the binding accordingly.
- Fixed FIFO-threshold interrupt wiring: set has_fifo on adis_data and
switch the DT interrupt type from edge to level, matching the
DEV_NOK signal this now uses (previously only the data-ready pulse
was wired, which never actually generated a FIFO interrupt).
- Moved the optional reset-status GPIO output into the base driver
patch (where it conceptually belongs) instead of bundling it into
the FIFO patch's USER_GPIO_CFG write; the FIFO patch now updates
that register with a read-modify-write instead of a blind write.
- Fixed cover letter: "Three compatible variants (-1, -2, -3)" was
wrong, there are only two (-2, -3); "configurable data-ready
interrupt pin (DIO1-4)" was wrong, there's a single fixed pin.
- Corrected spi-max-frequency to 10 MHz (datasheet maximum), from
15 MHz.
- Renamed ADIS16607_REG_SW_RES to ADIS16607_REG_SOFT_RESET to match
the datasheet's register name; renamed burst_buf to fifo_buf and
dropped the redundant "fifo_" prefix from the buffer postenable/
postdisable/ops names, since FIFO is the only capture mode and
"burst" no longer described what that scratch buffer is used for.
- Link to v1: https://lore.kernel.org/r/20261001-adis16607-v1-0-9c89cee35d56@analog.com
---
Radu Sabau (8):
iio: imu: adis: Fix custom reset dispatch in adislib
iio: imu: adis: Add optional self_test callback
iio: imu: adis: Add optional init callback
dt-bindings: iio: imu: Add bindings for ADI ADIS16607
iio: imu: Add driver for the ADI ADIS16607
iio: imu: adis16607: Add FIFO-based buffered/triggered capture
iio: imu: adis16607: Add external clock sync support
iio: imu: adis16607: Add calibration bias support for gyro/accel
.../devicetree/bindings/iio/imu/adi,adis16607.yaml | 94 ++
MAINTAINERS | 8 +
drivers/iio/imu/Kconfig | 13 +
drivers/iio/imu/Makefile | 1 +
drivers/iio/imu/adis.c | 15 +-
drivers/iio/imu/adis16607.c | 1496 ++++++++++++++++++++
include/linux/iio/imu/adis.h | 26 +-
7 files changed, 1648 insertions(+), 5 deletions(-)
---
base-commit: 0fcb05b72344f40806cbe810de8872f1698bf053
change-id: 20260617-adis16607-089ffc655ceb
Best regards,
--
Radu Sabau <radu.sabau@analog.com>
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 1/8] iio: imu: adis: Fix custom reset dispatch in adislib
2026-11-02 9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
@ 2026-11-02 9:25 ` Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 2/8] iio: imu: adis: Add optional self_test callback Radu Sabau via B4 Relay
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02 9:25 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron
From: Radu Sabau <radu.sabau@analog.com>
__adis_initial_startup()'s no-reset-gpio path called __adis_reset()
directly instead of adis->ops->reset(), silently ignoring any driver's
custom reset implementation. Invisible until now since
adis_default_ops.reset is __adis_reset() itself. Dispatch through
adis->ops->reset() instead - the same bug existed in the public
adis_reset() wrapper, fixed here too.
Move the settle-time sleep out of __adis_reset() and into both call
sites instead, so every reset op (default or custom) is treated
uniformly: adis->ops->reset() is expected to just do the write, and
the library always sleeps the settle time afterwards. This removes
the previous asymmetric "sleep here only for custom ops" special
case entirely.
Fixes: ecb010d44108 ("iio: imu: adis: Refactor adis_initial_startup")
Reviewed-by: Nuno Sá <nuno.sa@analog.com>
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/imu/adis.c | 6 ++----
include/linux/iio/imu/adis.h | 12 +++++++++++-
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c
index a2bc1d14ed91..793e3b6b0ea7 100644
--- a/drivers/iio/imu/adis.c
+++ b/drivers/iio/imu/adis.c
@@ -340,7 +340,6 @@ EXPORT_SYMBOL_NS_GPL(__adis_check_status, "IIO_ADISLIB");
int __adis_reset(struct adis *adis)
{
int ret;
- const struct adis_timeout *timeouts = adis->data->timeouts;
ret = __adis_write_reg_8(adis, adis->data->glob_cmd_reg,
ADIS_GLOB_CMD_SW_RESET);
@@ -349,8 +348,6 @@ int __adis_reset(struct adis *adis)
return ret;
}
- msleep(timeouts->sw_reset_ms);
-
return 0;
}
EXPORT_SYMBOL_NS_GPL(__adis_reset, "IIO_ADIS_LIB");
@@ -413,9 +410,10 @@ int __adis_initial_startup(struct adis *adis)
gpiod_set_value_cansleep(gpio, 0);
msleep(timeouts->reset_ms);
} else {
- ret = __adis_reset(adis);
+ ret = adis->ops->reset(adis);
if (ret)
return ret;
+ msleep(timeouts->sw_reset_ms);
}
ret = adis_self_test(adis);
diff --git a/include/linux/iio/imu/adis.h b/include/linux/iio/imu/adis.h
index bfb6df68e6c9..97323b8d1cb3 100644
--- a/include/linux/iio/imu/adis.h
+++ b/include/linux/iio/imu/adis.h
@@ -10,6 +10,7 @@
#define __IIO_ADIS_H__
#include <linux/cleanup.h>
+#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <linux/interrupt.h>
#include <linux/iio/iio.h>
@@ -171,8 +172,17 @@ int __adis_reset(struct adis *adis);
*/
static inline int adis_reset(struct adis *adis)
{
+ int ret;
+
guard(mutex)(&adis->state_lock);
- return __adis_reset(adis);
+
+ ret = adis->ops->reset(adis);
+ if (ret)
+ return ret;
+
+ msleep(adis->data->timeouts->sw_reset_ms);
+
+ return 0;
}
int __adis_write_reg(struct adis *adis, unsigned int reg,
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 2/8] iio: imu: adis: Add optional self_test callback
2026-11-02 9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 1/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
@ 2026-11-02 9:25 ` Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 3/8] iio: imu: adis: Add optional init callback Radu Sabau via B4 Relay
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02 9:25 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron
From: Radu Sabau <radu.sabau@analog.com>
Add an optional self_test callback to struct adis_data, for devices
whose self-test doesn't fit the generic mask-write/sleep/check-status
sequence. Purely additive: defaults to NULL, no change for existing
drivers.
Reviewed-by: Nuno Sá <nuno.sa@analog.com>
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/imu/adis.c | 3 +++
include/linux/iio/imu/adis.h | 7 +++++++
2 files changed, 10 insertions(+)
diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c
index 793e3b6b0ea7..5c3bb7259037 100644
--- a/drivers/iio/imu/adis.c
+++ b/drivers/iio/imu/adis.c
@@ -357,6 +357,9 @@ static int adis_self_test(struct adis *adis)
int ret;
const struct adis_timeout *timeouts = adis->data->timeouts;
+ if (adis->data->self_test)
+ return adis->data->self_test(adis);
+
ret = __adis_write_reg_16(adis, adis->data->self_test_reg,
adis->data->self_test_mask);
if (ret) {
diff --git a/include/linux/iio/imu/adis.h b/include/linux/iio/imu/adis.h
index 97323b8d1cb3..c09b381eb619 100644
--- a/include/linux/iio/imu/adis.h
+++ b/include/linux/iio/imu/adis.h
@@ -52,6 +52,12 @@ struct adis_timeout {
* @self_test_mask: Bitmask of supported self-test operations
* @self_test_reg: Register address to request self test command
* @self_test_no_autoclear: True if device's self-test needs clear of ctrl reg
+ * @self_test: Optional device-specific self-test override. When set,
+ * it is called instead of the generic mask-write/sleep/
+ * check-status sequence. The callback owns its own pass/fail
+ * determination entirely (it should call __adis_check_status()
+ * itself if it wants status-register checks). Must return 0 on
+ * success, a negative error code otherwise.
* @status_error_msgs: Array of error messages
* @status_error_mask: Bitmask of errors supported by the device
* @timeouts: Chip specific delays
@@ -82,6 +88,7 @@ struct adis_data {
unsigned int self_test_mask;
unsigned int self_test_reg;
bool self_test_no_autoclear;
+ int (*self_test)(struct adis *adis);
const struct adis_timeout *timeouts;
const char * const *status_error_msgs;
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 3/8] iio: imu: adis: Add optional init callback
2026-11-02 9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 1/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 2/8] iio: imu: adis: Add optional self_test callback Radu Sabau via B4 Relay
@ 2026-11-02 9:25 ` Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607 Radu Sabau via B4 Relay
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02 9:25 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron
From: Radu Sabau <radu.sabau@analog.com>
Add an optional init callback to struct adis_data, called right
after reset and before self-test. Not all ADIS devices need extra
bring-up between reset and self-test (SPI mode locking, status or
bootloader checks, pin or burst-mode configuration, etc.) - purely
additive: defaults to NULL, no change for existing drivers.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/imu/adis.c | 6 ++++++
include/linux/iio/imu/adis.h | 7 +++++++
2 files changed, 13 insertions(+)
diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c
index 5c3bb7259037..e2cf42b8b946 100644
--- a/drivers/iio/imu/adis.c
+++ b/drivers/iio/imu/adis.c
@@ -419,6 +419,12 @@ int __adis_initial_startup(struct adis *adis)
msleep(timeouts->sw_reset_ms);
}
+ if (adis->data->init) {
+ ret = adis->data->init(adis);
+ if (ret)
+ return ret;
+ }
+
ret = adis_self_test(adis);
if (ret)
return ret;
diff --git a/include/linux/iio/imu/adis.h b/include/linux/iio/imu/adis.h
index c09b381eb619..c9016f636530 100644
--- a/include/linux/iio/imu/adis.h
+++ b/include/linux/iio/imu/adis.h
@@ -52,6 +52,12 @@ struct adis_timeout {
* @self_test_mask: Bitmask of supported self-test operations
* @self_test_reg: Register address to request self test command
* @self_test_no_autoclear: True if device's self-test needs clear of ctrl reg
+ * @init: Optional device-specific hook called after reset,
+ * before self-test. Not all ADIS devices need extra bring-up
+ * between reset and self-test (SPI mode locking, status or
+ * bootloader checks, pin or burst-mode configuration, etc.) -
+ * only called when set. Must return 0 on success, a negative
+ * error code otherwise.
* @self_test: Optional device-specific self-test override. When set,
* it is called instead of the generic mask-write/sleep/
* check-status sequence. The callback owns its own pass/fail
@@ -88,6 +94,7 @@ struct adis_data {
unsigned int self_test_mask;
unsigned int self_test_reg;
bool self_test_no_autoclear;
+ int (*init)(struct adis *adis);
int (*self_test)(struct adis *adis);
const struct adis_timeout *timeouts;
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607
2026-11-02 9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
` (2 preceding siblings ...)
2026-11-02 9:25 ` [PATCH v5 3/8] iio: imu: adis: Add optional init callback Radu Sabau via B4 Relay
@ 2026-11-02 9:25 ` Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 5/8] iio: imu: Add driver for the " Radu Sabau via B4 Relay
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02 9:25 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron
From: Radu Sabau <radu.sabau@analog.com>
Add device tree bindings for the Analog Devices ADIS16607 Precision
MEMS Inertial Measurement Unit. The ADIS16607 integrates a triaxial
gyroscope, triaxial accelerometer, and temperature sensor, and
communicates over SPI Half-Duplex.
Two variants are supported, adis16607-2 and adis16607-3, differing
only in gyroscope full-scale range (±450°/s and ±2000°/s
respectively).
The binding exposes optional properties for:
- An external clock for sync mode (falling back to the internal 8 kHz
clock when absent)
- A hardware reset GPIO (active low)
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
.../devicetree/bindings/iio/imu/adi,adis16607.yaml | 94 ++++++++++++++++++++++
MAINTAINERS | 7 ++
2 files changed, 101 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml b/Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
new file mode 100644
index 000000000000..4545f942365c
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
@@ -0,0 +1,94 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/imu/adi,adis16607.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices ADIS16607 IMU
+
+maintainers:
+ - Radu Sabau <radu.sabau@analog.com>
+
+description: |
+ Analog Devices ADIS16607 Precision MEMS Inertial Measurement Unit (IMU)
+ with a triaxial gyroscope, triaxial accelerometer, and temperature sensor.
+ Uses SPI Half-Duplex communication protocol.
+
+ The device has five GPIO pins (GPIO0-GPIO4 in the datasheet), each
+ independently selectable for a single fixed function. This binding
+ exposes GPIO0's reset function as reset-gpios; GPIO2's sync function
+ is modeled via the clocks property, and GPIO4's DEV_NOK interrupt via
+ the interrupts property. GPIO3 (data-ready) is required internally
+ regardless of board wiring and isn't exposed. GPIO1 (wake) gates
+ whether the bootloader auto-loads its NVM contents after reset; it
+ defaults to enabled with a weak pull-up, which already gives normal
+ operation with no configuration needed, so it isn't exposed either.
+
+properties:
+ compatible:
+ enum:
+ - adi,adis16607-2
+ - adi,adis16607-3
+
+ reg:
+ maxItems: 1
+
+ spi-cpha: true
+
+ spi-cpol: true
+
+ spi-max-frequency:
+ maximum: 10000000
+
+ vdd-supply: true
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ description:
+ Optional external clock for sync mode. If not provided, the internal
+ 8 kHz clock is used.
+ maxItems: 1
+
+ reset-gpios:
+ description:
+ Optional GPIO for hardware reset, active low. Routes to the
+ device's GPIO0 pin, which can be independently set to the reset
+ function or left idle via an on-chip register.
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - spi-cpha
+ - spi-cpol
+ - vdd-supply
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/gpio/gpio.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ imu@0 {
+ compatible = "adi,adis16607-2";
+ reg = <0>;
+ spi-cpha;
+ spi-cpol;
+ spi-max-frequency = <10000000>;
+ vdd-supply = <&vdd>;
+ interrupts = <4 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&gpio>;
+ clocks = <&sync_clk>;
+ reset-gpios = <&gpio 10 GPIO_ACTIVE_LOW>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index f53047aa8d3c..d5679e8d0ffd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1792,6 +1792,13 @@ S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/imu/adi,adis16550.yaml
+ANALOG DEVICES INC ADIS16607 DRIVER
+M: Radu Sabau <radu.sabau@analog.com>
+L: linux-iio@vger.kernel.org
+S: Supported
+W: https://ez.analog.com/linux-software-drivers
+F: Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
+
ANALOG DEVICES INC ADL8113 DRIVER
M: Antoniu Miclaus <antoniu.miclaus@analog.com>
L: linux-iio@vger.kernel.org
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 5/8] iio: imu: Add driver for the ADI ADIS16607
2026-11-02 9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
` (3 preceding siblings ...)
2026-11-02 9:25 ` [PATCH v5 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607 Radu Sabau via B4 Relay
@ 2026-11-02 9:25 ` Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture Radu Sabau via B4 Relay
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02 9:25 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron
From: Radu Sabau <radu.sabau@analog.com>
The ADIS16607 is a Precision MEMS IMU from Analog Devices with
triaxial gyroscope and accelerometer, delta angle/velocity, and
temperature channels, communicating over a custom SPI Half-Duplex
protocol. ADIS16607-2 has a ±450°/s gyroscope range, ADIS16607-3
±2000°/s; both share a ±40g accelerometer range and an 8 kHz internal
clock.
This is the base driver: probe, raw channel reads, sampling-frequency
control, startup/self-test via adislib, and debugfs identity entries.
No buffered capture, external clock sync, or calibration bias yet -
those follow in later patches.
Self-test leaves the device's SELF_TEST register cleared on every exit
path, not just on success, so a read failure or a failed delta check
doesn't leave the sensor stuck in its self-test/force state until the
next reset.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
MAINTAINERS | 1 +
drivers/iio/imu/Kconfig | 13 +
drivers/iio/imu/Makefile | 1 +
drivers/iio/imu/adis16607.c | 894 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 909 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index d5679e8d0ffd..268a82adeb1d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1798,6 +1798,7 @@ L: linux-iio@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
+F: drivers/iio/imu/adis16607.c
ANALOG DEVICES INC ADL8113 DRIVER
M: Antoniu Miclaus <antoniu.miclaus@analog.com>
diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig
index 8bab4616be20..0b39057d0a7c 100644
--- a/drivers/iio/imu/Kconfig
+++ b/drivers/iio/imu/Kconfig
@@ -65,6 +65,19 @@ config ADIS16550
To compile this driver as a module, choose M here: the module will be
called adis16550.
+config ADIS16607
+ tristate "Analog Devices ADIS16607 and similar IMU driver"
+ depends on SPI
+ select IIO_ADIS_LIB
+ select IIO_ADIS_LIB_BUFFER if IIO_BUFFER
+ help
+ Say yes here to build support for Analog Devices ADIS16607-2
+ and ADIS16607-3 inertial sensors containing triaxis
+ gyroscope and triaxis accelerometer.
+
+ To compile this driver as a module, choose M here: the module will be
+ called adis16607.
+
source "drivers/iio/imu/bmi160/Kconfig"
source "drivers/iio/imu/bmi270/Kconfig"
source "drivers/iio/imu/bmi323/Kconfig"
diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile
index 3268dc2371ae..6eeec1ee387f 100644
--- a/drivers/iio/imu/Makefile
+++ b/drivers/iio/imu/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_ADIS16460) += adis16460.o
obj-$(CONFIG_ADIS16475) += adis16475.o
obj-$(CONFIG_ADIS16480) += adis16480.o
obj-$(CONFIG_ADIS16550) += adis16550.o
+obj-$(CONFIG_ADIS16607) += adis16607.o
adis_lib-y += adis.o
adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_trigger.o
diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
new file mode 100644
index 000000000000..5e2c999e68ac
--- /dev/null
+++ b/drivers/iio/imu/adis16607.c
@@ -0,0 +1,894 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ADIS16607 IMU driver
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/build_bug.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/imu/adis.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/seq_file.h>
+#include <linux/spi/spi.h>
+#include <linux/unaligned.h>
+#include <linux/units.h>
+
+/* Register addresses */
+#define ADIS16607_REG_DEV_ID 0x00
+#define ADIS16607_REG_REV_ID 0x01
+#define ADIS16607_REG_SERIAL_NUM0 0x02
+#define ADIS16607_REG_SERIAL_NUM1 0x03
+#define ADIS16607_REG_SERIAL_NUM2 0x04
+#define ADIS16607_REG_DIAG_STAT 0x05
+#define ADIS16607_REG_X_ACCEL 0x06
+#define ADIS16607_REG_Y_ACCEL 0x08
+#define ADIS16607_REG_Z_ACCEL 0x0A
+#define ADIS16607_REG_X_GYRO 0x0C
+#define ADIS16607_REG_Y_GYRO 0x0E
+#define ADIS16607_REG_Z_GYRO 0x10
+#define ADIS16607_REG_X_DELTVEL 0x12
+#define ADIS16607_REG_Y_DELTVEL 0x14
+#define ADIS16607_REG_Z_DELTVEL 0x16
+#define ADIS16607_REG_X_DELTANG 0x18
+#define ADIS16607_REG_Y_DELTANG 0x1A
+#define ADIS16607_REG_Z_DELTANG 0x1C
+#define ADIS16607_REG_TEMP 0x20
+#define ADIS16607_REG_USER_GPIO_CFG 0x2F
+#define ADIS16607_REG_SPI_HALFDUPLEX 0x32
+#define ADIS16607_REG_SOFT_RESET 0x36
+#define ADIS16607_REG_SELF_TEST 0x39
+#define ADIS16607_REG_DEC_RATE 0x3A
+#define ADIS16607_REG_DIGITAL_STATUS 0x4E
+
+/* SPI Half-Duplex lock value */
+#define ADIS16607_LOCK_SPI_HALFDUPLEX 0xB4B4
+
+/* Masks */
+#define ADIS16607_RESET_GPIO_MASK GENMASK(2, 0)
+#define ADIS16607_BOOTLOADER_BUSY_MASK BIT(0)
+
+/* Self-test registers and masks */
+#define ADIS16607_REG_SELF_TEST_DATA(x) ((x) + 0x23)
+#define ADIS16607_SNSR_SELF_TEST_MASK BIT(6)
+#define ADIS16607_ST_FORCE_MASK BIT(7)
+
+/* Self-test delta check limits */
+#define ADIS16607_ACCEL_XY_DELTA_MAX 260
+#define ADIS16607_ACCEL_Z_DELTA_MAX 4000
+#define ADIS16607_GYRO_DELTA_MAX 2600
+
+struct adis16607_chip_info {
+ const struct iio_chan_spec *channels;
+ const char *name;
+ u32 num_channels;
+ u32 gyro_max_val;
+ u32 gyro_max_scale;
+ u32 accel_max_val;
+ u32 accel_max_scale;
+ u32 temp_scale;
+ u32 deltang_max_val;
+ u32 deltvel_max_val;
+ u32 int_clk_Hz;
+ u16 max_dec;
+};
+
+struct adis16607 {
+ struct adis adis;
+ const struct adis16607_chip_info *info;
+ unsigned long clk_freq_mHz;
+ u64 sample_period_ns;
+};
+
+enum {
+ ADIS16607_SCAN_ACCEL_X,
+ ADIS16607_SCAN_ACCEL_Y,
+ ADIS16607_SCAN_ACCEL_Z,
+ ADIS16607_SCAN_GYRO_X,
+ ADIS16607_SCAN_GYRO_Y,
+ ADIS16607_SCAN_GYRO_Z,
+ ADIS16607_SCAN_DELTVEL_X,
+ ADIS16607_SCAN_DELTVEL_Y,
+ ADIS16607_SCAN_DELTVEL_Z,
+ ADIS16607_SCAN_DELTANG_X,
+ ADIS16607_SCAN_DELTANG_Y,
+ ADIS16607_SCAN_DELTANG_Z,
+ ADIS16607_SCAN_TEMP,
+ ADIS16607_SCAN_MAX,
+};
+
+struct adis16607_chan_regs {
+ u16 raw_reg;
+};
+
+static const struct adis16607_chan_regs adis16607_chan_regs[] = {
+ [ADIS16607_SCAN_ACCEL_X] = { ADIS16607_REG_X_ACCEL },
+ [ADIS16607_SCAN_ACCEL_Y] = { ADIS16607_REG_Y_ACCEL },
+ [ADIS16607_SCAN_ACCEL_Z] = { ADIS16607_REG_Z_ACCEL },
+ [ADIS16607_SCAN_GYRO_X] = { ADIS16607_REG_X_GYRO },
+ [ADIS16607_SCAN_GYRO_Y] = { ADIS16607_REG_Y_GYRO },
+ [ADIS16607_SCAN_GYRO_Z] = { ADIS16607_REG_Z_GYRO },
+ [ADIS16607_SCAN_DELTVEL_X] = { ADIS16607_REG_X_DELTVEL },
+ [ADIS16607_SCAN_DELTVEL_Y] = { ADIS16607_REG_Y_DELTVEL },
+ [ADIS16607_SCAN_DELTVEL_Z] = { ADIS16607_REG_Z_DELTVEL },
+ [ADIS16607_SCAN_DELTANG_X] = { ADIS16607_REG_X_DELTANG },
+ [ADIS16607_SCAN_DELTANG_Y] = { ADIS16607_REG_Y_DELTANG },
+ [ADIS16607_SCAN_DELTANG_Z] = { ADIS16607_REG_Z_DELTANG },
+ [ADIS16607_SCAN_TEMP] = { ADIS16607_REG_TEMP },
+};
+
+static_assert(ARRAY_SIZE(adis16607_chan_regs) == ADIS16607_SCAN_MAX);
+
+static int adis16607_spi_read(struct adis *adis, unsigned int reg,
+ unsigned int *val, unsigned int size)
+{
+ struct spi_message msg;
+ struct spi_transfer xfers[2] = {
+ {
+ .tx_buf = adis->tx,
+ .len = 4,
+ .cs_change = 1,
+ },
+ {
+ .tx_buf = adis->tx,
+ .rx_buf = adis->rx,
+ .len = 4,
+ },
+ };
+ u16 val_msw, val_lsw;
+ int ret;
+
+ /*
+ * Half-Duplex read: two 4-byte transfers with CS toggling in between.
+ * The first one latches the command, the second sends it again and
+ * the device answers in bytes 2-3.
+ */
+ switch (size) {
+ case 2:
+ adis->tx[0] = reg | BIT(7);
+
+ spi_message_init_with_transfers(&msg, xfers, ARRAY_SIZE(xfers));
+
+ ret = spi_sync(adis->spi, &msg);
+ if (ret)
+ return ret;
+
+ *val = get_unaligned_be16(&adis->rx[2]);
+ return 0;
+
+ case 4:
+ /* Read MSW from reg */
+ adis->tx[0] = reg | BIT(7);
+
+ spi_message_init_with_transfers(&msg, xfers, ARRAY_SIZE(xfers));
+
+ ret = spi_sync(adis->spi, &msg);
+ if (ret)
+ return ret;
+
+ val_msw = get_unaligned_be16(&adis->rx[2]);
+
+ /* Read LSW from reg+1 */
+ adis->tx[0] = (reg + 1) | BIT(7);
+
+ spi_message_init_with_transfers(&msg, xfers, ARRAY_SIZE(xfers));
+
+ ret = spi_sync(adis->spi, &msg);
+ if (ret)
+ return ret;
+
+ val_lsw = adis->rx[2];
+
+ /*
+ * Combine: 24-bit value with MSW in upper 16 bits,
+ * LSW in lower 8 bits.
+ */
+ *val = FIELD_PREP(GENMASK(23, 8), val_msw) | val_lsw;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+/*
+ * SPI Half-Duplex write operation for ADIS16607.
+ * Protocol:
+ * - Send 3 bytes: [reg, val_high, val_low]
+ * - Only 16-bit writes are supported
+ */
+static int adis16607_spi_write(struct adis *adis, unsigned int reg,
+ unsigned int val, unsigned int size)
+{
+ if (size != 2)
+ return -EINVAL;
+
+ adis->tx[0] = reg;
+ put_unaligned_be16(val, &adis->tx[1]);
+
+ return spi_write(adis->spi, adis->tx, 3);
+}
+
+static int adis16607_reset(struct adis *adis)
+{
+ return adis16607_spi_write(adis, ADIS16607_REG_SOFT_RESET, BIT(0), 2);
+}
+
+static const struct adis_ops adis16607_ops = {
+ .read = adis16607_spi_read,
+ .write = adis16607_spi_write,
+ .reset = adis16607_reset,
+};
+
+/*
+ * Check if the delta between two values is within specified range.
+ */
+static bool adis16607_delta_check(s16 val1, s16 val2, s16 delta_max)
+{
+ return abs(val1 - val2) <= delta_max;
+}
+
+/*
+ * Perform sensor self-test for ADIS16607.
+ * Reads self-test data before and after forcing self-test,
+ * then verifies delta values are within acceptable ranges.
+ */
+static int adis16607_sensor_self_test(struct adis16607 *st)
+{
+ struct adis *adis = &st->adis;
+ u16 st1_val[6], st2_val[6];
+ s16 delta_max[6] = {
+ ADIS16607_ACCEL_XY_DELTA_MAX,
+ ADIS16607_ACCEL_XY_DELTA_MAX,
+ ADIS16607_ACCEL_Z_DELTA_MAX,
+ ADIS16607_GYRO_DELTA_MAX,
+ ADIS16607_GYRO_DELTA_MAX,
+ ADIS16607_GYRO_DELTA_MAX,
+ };
+ int ret;
+
+ /* Enable sensor self-test */
+ ret = __adis_write_reg_16(adis, ADIS16607_REG_SELF_TEST,
+ ADIS16607_SNSR_SELF_TEST_MASK);
+ if (ret)
+ return ret;
+
+ /* Wait for self-test to settle before reading results */
+ msleep(adis->data->timeouts->self_test_ms);
+
+ /* Read first set of self-test data */
+ for (unsigned int i = 0; i < ARRAY_SIZE(st1_val); i++) {
+ ret = __adis_read_reg_16(adis, ADIS16607_REG_SELF_TEST_DATA(i),
+ &st1_val[i]);
+ if (ret)
+ goto clear_self_test;
+ }
+
+ /* Force self-test */
+ ret = __adis_write_reg_16(adis, ADIS16607_REG_SELF_TEST,
+ ADIS16607_SNSR_SELF_TEST_MASK | ADIS16607_ST_FORCE_MASK);
+ if (ret)
+ goto clear_self_test;
+
+ /* Wait for forced self-test to settle before reading results */
+ msleep(adis->data->timeouts->self_test_ms);
+
+ /* Read second set of self-test data */
+ for (unsigned int i = 0; i < ARRAY_SIZE(st2_val); i++) {
+ ret = __adis_read_reg_16(adis, ADIS16607_REG_SELF_TEST_DATA(i),
+ &st2_val[i]);
+ if (ret)
+ goto clear_self_test;
+
+ if (!adis16607_delta_check((s16)st1_val[i], (s16)st2_val[i],
+ delta_max[i])) {
+ ret = dev_err_probe(&adis->spi->dev, -EINVAL,
+ "Self-test failed for axis %u: delta out of range\n",
+ i);
+ goto clear_self_test;
+ }
+ }
+
+ /* Clear self-test register */
+ return __adis_write_reg_16(adis, ADIS16607_REG_SELF_TEST, 0);
+
+clear_self_test:
+ /*
+ * Best-effort cleanup after a failure above - leave the device out
+ * of its self-test/force state rather than stuck in it until the
+ * next reset. Warn rather than returning this write's own result,
+ * so a cleanup failure doesn't mask the real error that got us here.
+ */
+ if (__adis_write_reg_16(adis, ADIS16607_REG_SELF_TEST, 0))
+ dev_warn(&adis->spi->dev, "failed to clear self-test register\n");
+
+ return ret;
+}
+
+/*
+ * Called by __adis_initial_startup() right after reset, before self-test:
+ * SPI Half-Duplex locking, dev_id verification and the bootloader check.
+ */
+static int adis16607_init(struct adis *adis)
+{
+ struct device *dev = &adis->spi->dev;
+ u16 diag_stat, digital_status, dev_id;
+ int ret;
+
+ /* Lock the device into SPI Half-Duplex mode */
+ ret = __adis_write_reg_16(adis, ADIS16607_REG_SPI_HALFDUPLEX,
+ ADIS16607_LOCK_SPI_HALFDUPLEX);
+ if (ret)
+ return ret;
+
+ /* Verify the correct device is attached */
+ ret = __adis_read_reg_16(adis, ADIS16607_REG_DEV_ID, &dev_id);
+ if (ret)
+ return ret;
+
+ if (dev_id != adis->data->prod_id)
+ return dev_err_probe(dev, -ENODEV,
+ "device ID mismatch: expected 0x%04x, got 0x%04x\n",
+ adis->data->prod_id, dev_id);
+
+ /* Read diag_stat to clear any latched faults */
+ ret = __adis_read_reg_16(adis, ADIS16607_REG_DIAG_STAT, &diag_stat);
+ if (ret)
+ return ret;
+
+ ret = __adis_check_status(adis);
+ if (ret)
+ return ret;
+
+ /* Check that bootloader is not busy */
+ ret = __adis_read_reg_16(adis, ADIS16607_REG_DIGITAL_STATUS, &digital_status);
+ if (ret)
+ return ret;
+
+ if (digital_status & ADIS16607_BOOTLOADER_BUSY_MASK)
+ return dev_err_probe(dev, -EBUSY, "bootloader is busy\n");
+
+ return 0;
+}
+
+/*
+ * Self-test callback for struct adis_data (see adis16607_data below).
+ * Called by __adis_initial_startup() right after adis16607_init().
+ */
+static int adis16607_self_test(struct adis *adis)
+{
+ struct adis16607 *st = container_of(adis, struct adis16607, adis);
+ u16 diag_stat;
+ int ret;
+
+ /*
+ * Inertial Settling Time (Table 1), needed before self-test
+ * reads sensor data.
+ */
+ msleep(40);
+
+ ret = adis16607_sensor_self_test(st);
+ if (ret)
+ return ret;
+
+ /* Read diag_stat again to clear any faults latched during self-test */
+ ret = __adis_read_reg_16(adis, ADIS16607_REG_DIAG_STAT, &diag_stat);
+ if (ret)
+ return ret;
+
+ return __adis_check_status(adis);
+}
+
+static int adis16607_firmware_revision_show(struct seq_file *s, void *arg)
+{
+ struct adis16607 *st = s->private;
+ struct {
+ u8 minor;
+ u8 major;
+ } rev;
+ u16 tmp;
+ int ret;
+
+ ret = adis_read_reg_16(&st->adis, ADIS16607_REG_REV_ID, &tmp);
+ if (ret)
+ return ret;
+
+ rev.major = FIELD_GET(GENMASK(15, 8), tmp);
+ rev.minor = FIELD_GET(GENMASK(7, 0), tmp);
+
+ seq_printf(s, "%u.%u\n", rev.major, rev.minor);
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(adis16607_firmware_revision);
+
+static int adis16607_show_serial_number(void *arg, u64 *val)
+{
+ struct adis16607 *st = arg;
+ u64 serial;
+ u16 tmp;
+ int ret;
+
+ ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM2, &tmp);
+ if (ret)
+ return ret;
+
+ serial = tmp;
+
+ ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM1, &tmp);
+ if (ret)
+ return ret;
+
+ serial <<= 16;
+ serial |= tmp;
+
+ ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM0, &tmp);
+ if (ret)
+ return ret;
+
+ serial <<= 16;
+ serial |= tmp;
+
+ *val = serial;
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(adis16607_serial_number_fops,
+ adis16607_show_serial_number, NULL, "0x%.8llx\n");
+
+static int adis16607_show_dev_id(void *arg, u64 *val)
+{
+ struct adis16607 *st = arg;
+ u16 dev_id;
+ int ret;
+
+ ret = adis_read_reg_16(&st->adis, ADIS16607_REG_DEV_ID, &dev_id);
+ if (ret)
+ return ret;
+
+ *val = dev_id;
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(adis16607_dev_id_fops,
+ adis16607_show_dev_id, NULL, "%llu\n");
+
+static void adis16607_debugfs_init(struct iio_dev *indio_dev)
+{
+ struct adis16607 *st = iio_priv(indio_dev);
+ struct dentry *d = iio_get_debugfs_dentry(indio_dev);
+
+ debugfs_create_file_unsafe("serial_number", 0400, d, st,
+ &adis16607_serial_number_fops);
+ debugfs_create_file_unsafe("dev_id", 0400, d, st,
+ &adis16607_dev_id_fops);
+ debugfs_create_file("firmware_revision", 0400, d, st,
+ &adis16607_firmware_revision_fops);
+}
+
+static int adis16607_get_freq(struct adis16607 *st, u32 *freq)
+{
+ u16 dec;
+ int ret;
+
+ ret = adis_read_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, &dec);
+ if (ret)
+ return ret;
+
+ *freq = DIV_ROUND_CLOSEST(st->clk_freq_mHz, dec + 1);
+
+ return 0;
+}
+
+static int adis16607_set_freq(struct adis16607 *st, u32 freq)
+{
+ u64 period_ns;
+ u32 dec;
+ int ret;
+
+ if (!freq)
+ return -EINVAL;
+
+ dec = DIV_ROUND_CLOSEST(st->clk_freq_mHz, freq);
+ if (dec)
+ dec--;
+
+ dec = min(dec, st->info->max_dec);
+
+ /*
+ * The sample period is (dec + 1) / clk_freq. clk_freq_mHz is in
+ * millihertz, hence the MILLIHZ_PER_HZ factor to get back to ns.
+ * Starting from a u64 forces every multiplication below to 64 bits:
+ * NSEC_PER_SEC * MILLIHZ_PER_HZ alone (~1e12) overflows 32 bits.
+ */
+ period_ns = NSEC_PER_SEC;
+ period_ns *= MILLIHZ_PER_HZ;
+ period_ns *= dec + 1;
+ period_ns = DIV_ROUND_CLOSEST_ULL(period_ns, st->clk_freq_mHz);
+
+ adis_dev_auto_scoped_lock(&st->adis) {
+ /* Reset the decimation accumulator before the new value */
+ ret = __adis_write_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, 0);
+ if (ret)
+ return ret;
+
+ if (dec) {
+ ret = __adis_write_reg_16(&st->adis,
+ ADIS16607_REG_DEC_RATE, dec);
+ if (ret)
+ return ret;
+ }
+
+ st->sample_period_ns = period_ns;
+ }
+
+ return 0;
+}
+
+/*
+ * .address is an index into adis16607_chan_regs[] for this driver, not a
+ * literal register like adis_single_conversion() expects - resolve it on a
+ * local copy rather than reimplementing the helper's locking and
+ * sign-extension logic ourselves.
+ */
+static int adis16607_raw_read(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan, int *val)
+{
+ struct iio_chan_spec raw_chan = *chan;
+
+ raw_chan.address = adis16607_chan_regs[chan->address].raw_reg;
+
+ return adis_single_conversion(indio_dev, &raw_chan, 0, val);
+}
+
+static int adis16607_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long info)
+{
+ struct adis16607 *st = iio_priv(indio_dev);
+ int ret;
+ u32 tmp;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ return adis16607_raw_read(indio_dev, chan, val);
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_ANGL_VEL:
+ *val = st->info->gyro_max_val;
+ *val2 = st->info->gyro_max_scale;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_ACCEL:
+ *val = st->info->accel_max_val;
+ *val2 = st->info->accel_max_scale;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_TEMP:
+ *val = st->info->temp_scale;
+ return IIO_VAL_INT;
+ case IIO_DELTA_ANGL:
+ *val = st->info->deltang_max_val;
+ *val2 = 23;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_DELTA_VELOCITY:
+ *val = st->info->deltvel_max_val;
+ *val2 = 23;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ /* Temperature: 0 raw counts = 25°C; offset = 25000 mdeg / 5 mdeg per LSB = 5000 */
+ *val = 5000;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = adis16607_get_freq(st, &tmp);
+ if (ret)
+ return ret;
+ /* tmp is in mHz: split into Hz and the remainder in uHz */
+ *val = tmp / MILLIHZ_PER_HZ;
+ *val2 = (tmp % MILLIHZ_PER_HZ) *
+ (MICROHZ_PER_HZ / MILLIHZ_PER_HZ);
+ return IIO_VAL_INT_PLUS_MICRO;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adis16607_write_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int val, int val2, long info)
+{
+ struct adis16607 *st = iio_priv(indio_dev);
+ u32 tmp;
+
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (val < 0 || val2 < 0)
+ return -EINVAL;
+
+ /* val is in Hz and val2 in uHz: convert both to mHz */
+ tmp = val * MILLIHZ_PER_HZ +
+ val2 / (MICROHZ_PER_HZ / MILLIHZ_PER_HZ);
+ return adis16607_set_freq(st, tmp);
+
+ default:
+ return -EINVAL;
+ }
+}
+
+/*
+ * .address indexes adis16607_chan_regs[], .scan_index is the buffer position;
+ * both come from the same enum today but are separate fields on purpose.
+ */
+#define ADIS16607_MOD_CHAN(_type, _mod, _idx) \
+ { \
+ .type = (_type), \
+ .modified = 1, \
+ .channel2 = (_mod), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .address = (_idx), \
+ .scan_index = (_idx), \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 24, \
+ .storagebits = 32, \
+ .endianness = IIO_BE, \
+ }, \
+ }
+
+#define ADIS16607_ACCEL_CHANNEL(_mod) \
+ ADIS16607_MOD_CHAN(IIO_ACCEL, IIO_MOD_ ## _mod, \
+ ADIS16607_SCAN_ACCEL_ ## _mod)
+
+#define ADIS16607_GYRO_CHANNEL(_mod) \
+ ADIS16607_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_ ## _mod, \
+ ADIS16607_SCAN_GYRO_ ## _mod)
+
+#define ADIS16607_MOD_CHAN_DELTA(_type, _mod, _idx) \
+ { \
+ .type = (_type), \
+ .modified = 1, \
+ .channel2 = (_mod), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .address = (_idx), \
+ .scan_index = (_idx), \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 24, \
+ .storagebits = 32, \
+ .endianness = IIO_BE, \
+ }, \
+ }
+
+#define ADIS16607_DELTANG_CHAN(_mod) \
+ ADIS16607_MOD_CHAN_DELTA(IIO_DELTA_ANGL, IIO_MOD_ ## _mod, \
+ ADIS16607_SCAN_DELTANG_ ## _mod)
+
+#define ADIS16607_DELTVEL_CHAN(_mod) \
+ ADIS16607_MOD_CHAN_DELTA(IIO_DELTA_VELOCITY, IIO_MOD_ ## _mod, \
+ ADIS16607_SCAN_DELTVEL_ ## _mod)
+
+static const struct iio_chan_spec adis16607_channels[] = {
+ ADIS16607_ACCEL_CHANNEL(X),
+ ADIS16607_ACCEL_CHANNEL(Y),
+ ADIS16607_ACCEL_CHANNEL(Z),
+ ADIS16607_GYRO_CHANNEL(X),
+ ADIS16607_GYRO_CHANNEL(Y),
+ ADIS16607_GYRO_CHANNEL(Z),
+ ADIS16607_DELTVEL_CHAN(X),
+ ADIS16607_DELTVEL_CHAN(Y),
+ ADIS16607_DELTVEL_CHAN(Z),
+ ADIS16607_DELTANG_CHAN(X),
+ ADIS16607_DELTANG_CHAN(Y),
+ ADIS16607_DELTANG_CHAN(Z),
+ {
+ .type = IIO_TEMP,
+ .indexed = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .address = ADIS16607_SCAN_TEMP,
+ .scan_index = ADIS16607_SCAN_TEMP,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_BE,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(ADIS16607_SCAN_MAX),
+};
+
+static const struct iio_info adis16607_info = {
+ .read_raw = adis16607_read_raw,
+ .write_raw = adis16607_write_raw,
+ .debugfs_reg_access = adis_debugfs_reg_access,
+};
+
+static const char * const adis16607_status_error_msgs[] = {
+ [9] = "Boot Memory Failure",
+ [11] = "Power Supply Failure",
+ [12] = "Accelerometer Failure",
+ [13] = "Gyroscope Failure",
+};
+
+static const struct adis_timeout adis16607_timeouts = {
+ .reset_ms = 130,
+ .sw_reset_ms = 50,
+ .self_test_ms = 15,
+};
+
+static const struct adis_data adis16607_data = {
+ .diag_stat_reg = ADIS16607_REG_DIAG_STAT,
+ .diag_stat_size = 2,
+ .prod_id_reg = ADIS16607_REG_DEV_ID,
+ .prod_id = 0x6000,
+ .unmasked_drdy = true,
+ .init = adis16607_init,
+ .self_test = adis16607_self_test,
+ .status_error_msgs = adis16607_status_error_msgs,
+ .status_error_mask = BIT(9) | BIT(11) | BIT(12) | BIT(13),
+ .timeouts = &adis16607_timeouts,
+};
+
+/*
+ * ADIS16607-2: 450 deg/s gyro, 40g accel
+ * ADIS16607-3: 2000 deg/s gyro, 40g accel
+ */
+static const struct adis16607_chip_info adis16607_2_chip_info = {
+ .channels = adis16607_channels,
+ .num_channels = ARRAY_SIZE(adis16607_channels),
+ .name = "adis16607-2",
+ .gyro_max_val = IIO_DEGREE_TO_RAD(450),
+ .gyro_max_scale = 30000 << 8,
+ .accel_max_val = IIO_G_TO_M_S_2(40),
+ .accel_max_scale = 31250 << 8,
+ .temp_scale = 5,
+ .deltang_max_val = IIO_DEGREE_TO_RAD(450),
+ .deltvel_max_val = IIO_G_TO_M_S_2(40),
+ .int_clk_Hz = 8000,
+ .max_dec = 65535,
+};
+
+static const struct adis16607_chip_info adis16607_3_chip_info = {
+ .channels = adis16607_channels,
+ .num_channels = ARRAY_SIZE(adis16607_channels),
+ .name = "adis16607-3",
+ .gyro_max_val = IIO_DEGREE_TO_RAD(2000),
+ .gyro_max_scale = 31250 << 8,
+ .accel_max_val = IIO_G_TO_M_S_2(40),
+ .accel_max_scale = 31250 << 8,
+ .temp_scale = 5,
+ .deltang_max_val = IIO_DEGREE_TO_RAD(2000),
+ .deltvel_max_val = IIO_G_TO_M_S_2(40),
+ .int_clk_Hz = 8000,
+ .max_dec = 65535,
+};
+
+/*
+ * No external clock sync support yet: always run on the internal clock.
+ * Default dec=0 after reset, so period = 1 / clk_freq_mHz.
+ */
+static int adis16607_config_clk(struct adis16607 *st)
+{
+ u64 period_ns = NSEC_PER_SEC;
+
+ st->clk_freq_mHz = st->info->int_clk_Hz * MILLIHZ_PER_HZ;
+
+ /* clk_freq_mHz is in millihertz: scale to get the period in ns */
+ period_ns *= MILLIHZ_PER_HZ;
+ st->sample_period_ns = DIV_ROUND_CLOSEST_ULL(period_ns,
+ st->clk_freq_mHz);
+ return 0;
+}
+
+static int adis16607_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct adis16607 *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->info = spi_get_device_match_data(spi);
+ if (!st->info)
+ return -ENODATA;
+
+ indio_dev->name = st->info->name;
+ indio_dev->channels = st->info->channels;
+ indio_dev->num_channels = st->info->num_channels;
+ indio_dev->info = &adis16607_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ st->adis.ops = &adis16607_ops;
+
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get vdd regulator\n");
+
+ ret = adis_init(&st->adis, indio_dev, spi, &adis16607_data);
+ if (ret)
+ return ret;
+
+ /*
+ * Force GPIO0 back to its RESET function before relying on toggling
+ * it below - RESET_GPIO_MASK is runtime-writable on this part, so a
+ * prior write (buggy driver, direct reg access, etc.) could have
+ * left it de-routed, which would make the GPIO toggle in
+ * __adis_initial_startup() silently do nothing. This write goes out
+ * over SPI, which stays live regardless of this mask's state, so it
+ * can re-arm the mux as long as the device is merely
+ * reset-pin-defeated and not fully unresponsive. Only warn on
+ * failure instead of erroring out - bailing here would throw away
+ * whatever chance the GPIO toggle below still has of working.
+ */
+ if (device_property_present(dev, "reset-gpios")) {
+ ret = __adis_update_bits_base(&st->adis, ADIS16607_REG_USER_GPIO_CFG,
+ ADIS16607_RESET_GPIO_MASK,
+ FIELD_PREP(ADIS16607_RESET_GPIO_MASK, 1), 2);
+ if (ret)
+ dev_warn(dev, "failed to force reset-pin routing: %d\n", ret);
+ }
+
+ ret = __adis_initial_startup(&st->adis);
+ if (ret)
+ return ret;
+
+ ret = adis16607_config_clk(st);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret)
+ return ret;
+
+ adis16607_debugfs_init(indio_dev);
+
+ return 0;
+}
+
+static const struct spi_device_id adis16607_id[] = {
+ { .name = "adis16607-2", .driver_data = (kernel_ulong_t)&adis16607_2_chip_info },
+ { .name = "adis16607-3", .driver_data = (kernel_ulong_t)&adis16607_3_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, adis16607_id);
+
+static const struct of_device_id adis16607_of_match[] = {
+ { .compatible = "adi,adis16607-2", .data = &adis16607_2_chip_info },
+ { .compatible = "adi,adis16607-3", .data = &adis16607_3_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adis16607_of_match);
+
+static struct spi_driver adis16607_driver = {
+ .driver = {
+ .name = "adis16607",
+ .of_match_table = adis16607_of_match,
+ },
+ .probe = adis16607_probe,
+ .id_table = adis16607_id,
+};
+module_spi_driver(adis16607_driver);
+
+MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADIS16607 and similar IMU driver");
+MODULE_IMPORT_NS("IIO_ADISLIB");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture
2026-11-02 9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
` (4 preceding siblings ...)
2026-11-02 9:25 ` [PATCH v5 5/8] iio: imu: Add driver for the " Radu Sabau via B4 Relay
@ 2026-11-02 9:25 ` Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 7/8] iio: imu: adis16607: Add external clock sync support Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel Radu Sabau via B4 Relay
7 siblings, 0 replies; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02 9:25 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron
From: Radu Sabau <radu.sabau@analog.com>
Add buffered capture via the device's hardware FIFO - the only
capture mode this driver supports. Popping the FIFO uses a dedicated
command and payload format (FIFO_DATA_CMD, 52 bytes, no checksum),
handled directly rather than through adislib's generic burst-read
helpers, which assume one fixed-shape read per trigger rather than a
variable-count drain loop.
The watermark-met condition is signaled via DEV_NOK on the device's
one interrupt pin, which stays asserted (level-triggered) until
DIAG_STAT is read and cleared - matching adislib's has_fifo contract.
DEV_NOK fires on any DIAG_STAT flag, not just FIFO_THRESH_MET, so the
trigger handler checks that specific bit before popping anything.
DR_GPIO_MASK stays enabled unconditionally: DR pulses drive the FIFO
controller's internal sample enqueue, independent of pin routing.
Watermark is configurable via the standard hwfifo_set_watermark
callback and the usual hwfifo_* sysfs attributes.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/imu/adis16607.c | 392 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 388 insertions(+), 4 deletions(-)
diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
index 5e2c999e68ac..bd7752f7a1ec 100644
--- a/drivers/iio/imu/adis16607.c
+++ b/drivers/iio/imu/adis16607.c
@@ -13,6 +13,10 @@
#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
#include <linux/iio/imu/adis.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
#include <linux/math.h>
#include <linux/minmax.h>
#include <linux/module.h>
@@ -43,8 +47,11 @@
#define ADIS16607_REG_Y_DELTANG 0x1A
#define ADIS16607_REG_Z_DELTANG 0x1C
#define ADIS16607_REG_TEMP 0x20
+#define ADIS16607_REG_FIFO_WORD_CNT 0x2B
#define ADIS16607_REG_USER_GPIO_CFG 0x2F
#define ADIS16607_REG_SPI_HALFDUPLEX 0x32
+#define ADIS16607_REG_USER_DATA_CFG 0x34
+#define ADIS16607_REG_USER_FIFO_CFG 0x35
#define ADIS16607_REG_SOFT_RESET 0x36
#define ADIS16607_REG_SELF_TEST 0x39
#define ADIS16607_REG_DEC_RATE 0x3A
@@ -55,8 +62,29 @@
/* Masks */
#define ADIS16607_RESET_GPIO_MASK GENMASK(2, 0)
+#define ADIS16607_DR_GPIO_MASK GENMASK(11, 9)
+#define ADIS16607_DEV_NOK_MASK GENMASK(14, 12)
+#define ADIS16607_DATA_CNTR_EN_MASK BIT(14)
+#define ADIS16607_BURST32_MASK BIT(15)
#define ADIS16607_BOOTLOADER_BUSY_MASK BIT(0)
+/* FIFO masks */
+#define ADIS16607_FIFO_THR_MASK GENMASK(10, 0)
+#define ADIS16607_FIFO_FLUSH_MASK BIT(15)
+#define ADIS16607_FIFO_THR_MET_MASK BIT(0)
+#define ADIS16607_FIFO_DATA_CMD 0xA9
+
+/* FIFO limits */
+#define ADIS16607_MAX_FIFO_WM 38
+#define ADIS16607_FIFO_WORDS_PER_SAMPLE 26
+#define ADIS16607_FIFO_DATA_LEN 52
+
+/* FIFO_DATA read - Half-Duplex: 2-byte command phase, then the FIFO data */
+#define ADIS16607_FIFO_DATA_CMD_SIZE 2
+
+/* Largest scan: 12 channels of 24 bits stored as 32, plus 16-bit temperature */
+#define ADIS16607_SCAN_DATA_SIZE (12 * sizeof(s32) + sizeof(s16))
+
/* Self-test registers and masks */
#define ADIS16607_REG_SELF_TEST_DATA(x) ((x) + 0x23)
#define ADIS16607_SNSR_SELF_TEST_MASK BIT(6)
@@ -87,6 +115,12 @@ struct adis16607 {
const struct adis16607_chip_info *info;
unsigned long clk_freq_mHz;
u64 sample_period_ns;
+ u16 fifo_watermark;
+ /* IIO buffer for reordered data - sized for max channels */
+ IIO_DECLARE_BUFFER_WITH_TS(u8, data, ADIS16607_SCAN_DATA_SIZE);
+ /* Scratch buffer used for FIFO pop operations, see adis16607_fifo_pop() */
+ u8 fifo_buf[ADIS16607_FIFO_DATA_CMD_SIZE + ADIS16607_FIFO_DATA_LEN]
+ __aligned(IIO_DMA_MINALIGN);
};
enum {
@@ -229,6 +263,304 @@ static const struct adis_ops adis16607_ops = {
.reset = adis16607_reset,
};
+/*
+ * FIFO/burst data offsets for each channel (in bytes from the data start,
+ * i.e. after the 4-byte command prefix).
+ * Each 32-bit inertial channel occupies 4 bytes (MSW + LSW), TEMP is 2 bytes.
+ *
+ * FIFO data layout (52 bytes, DATA_CNTR enabled, no checksum):
+ * Offset 0-3: ACCEL_X (32-bit: MSW, LSW)
+ * Offset 4-7: ACCEL_Y
+ * Offset 8-11: ACCEL_Z
+ * Offset 12-15: GYRO_X
+ * Offset 16-19: GYRO_Y
+ * Offset 20-23: GYRO_Z
+ * Offset 24-27: DELTVEL_X
+ * Offset 28-31: DELTVEL_Y
+ * Offset 32-35: DELTVEL_Z
+ * Offset 36-39: DELTANG_X
+ * Offset 40-43: DELTANG_Y
+ * Offset 44-47: DELTANG_Z
+ * Offset 48-49: TEMP (16-bit)
+ * Offset 50-51: DATA_CNTR (16-bit)
+ *
+ * Each 32-bit value encodes 24-bit sensor data as:
+ * value_24bit = (MSW << 8) | (LSW >> 8)
+ */
+static const u8 adis16607_burst_offsets[] = {
+ [ADIS16607_SCAN_ACCEL_X] = 0,
+ [ADIS16607_SCAN_ACCEL_Y] = 4,
+ [ADIS16607_SCAN_ACCEL_Z] = 8,
+ [ADIS16607_SCAN_GYRO_X] = 12,
+ [ADIS16607_SCAN_GYRO_Y] = 16,
+ [ADIS16607_SCAN_GYRO_Z] = 20,
+ [ADIS16607_SCAN_DELTVEL_X] = 24,
+ [ADIS16607_SCAN_DELTVEL_Y] = 28,
+ [ADIS16607_SCAN_DELTVEL_Z] = 32,
+ [ADIS16607_SCAN_DELTANG_X] = 36,
+ [ADIS16607_SCAN_DELTANG_Y] = 40,
+ [ADIS16607_SCAN_DELTANG_Z] = 44,
+ [ADIS16607_SCAN_TEMP] = 48,
+};
+
+/*
+ * Pop a single sample from FIFO using FIFO_DATA command (0xA9).
+ * Returns the raw FIFO data in the provided buffer.
+ */
+static int adis16607_fifo_pop(struct adis16607 *st, u8 *data)
+{
+ struct spi_message msg;
+ struct spi_transfer xfer = {
+ .tx_buf = st->fifo_buf,
+ .rx_buf = st->fifo_buf,
+ .len = ADIS16607_FIFO_DATA_CMD_SIZE + ADIS16607_FIFO_DATA_LEN,
+ };
+ int ret;
+
+ /* FIFO data command */
+ st->fifo_buf[0] = ADIS16607_FIFO_DATA_CMD;
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ ret = spi_sync(st->adis.spi, &msg);
+ if (ret)
+ return ret;
+
+ /* Copy data starting after command bytes */
+ memcpy(data, st->fifo_buf + ADIS16607_FIFO_DATA_CMD_SIZE, ADIS16607_FIFO_DATA_LEN);
+ return 0;
+}
+
+/*
+ * Process a single FIFO sample and push to IIO buffer.
+ * FIFO data format differs from regular burst - no DIAG_STAT, data starts immediately.
+ *
+ * FIFO data layout (52 bytes):
+ * Offset 0-3: ACCEL_X (32-bit)
+ * Offset 4-7: ACCEL_Y (32-bit)
+ * Offset 8-11: ACCEL_Z (32-bit)
+ * Offset 12-15: GYRO_X (32-bit)
+ * Offset 16-19: GYRO_Y (32-bit)
+ * Offset 20-23: GYRO_Z (32-bit)
+ * Offset 24-27: DELTVEL_X (32-bit)
+ * Offset 28-31: DELTVEL_Y (32-bit)
+ * Offset 32-35: DELTVEL_Z (32-bit)
+ * Offset 36-39: DELTANG_X (32-bit)
+ * Offset 40-43: DELTANG_Y (32-bit)
+ * Offset 44-47: DELTANG_Z (32-bit)
+ * Offset 48-49: TEMP (16-bit)
+ * Offset 50-51: DATA_CNTR (16-bit)
+ */
+static void adis16607_fifo_push_sample(struct iio_dev *indio_dev,
+ u8 *fifo_data, s64 timestamp)
+{
+ struct adis16607 *st = iio_priv(indio_dev);
+ u16 msw, lsw;
+ s32 val24;
+ int bit, offset = 0;
+
+ memset(st->data, 0, sizeof(st->data));
+
+ iio_for_each_active_channel(indio_dev, bit) {
+ u8 burst_off;
+
+ /* Skip the soft timestamp channel - it has no burst offset */
+ if (bit >= ARRAY_SIZE(adis16607_burst_offsets))
+ continue;
+
+ burst_off = adis16607_burst_offsets[bit];
+
+ if (bit == ADIS16607_SCAN_TEMP) {
+ /* TEMP is 16-bit */
+ put_unaligned_be16(get_unaligned_be16(&fifo_data[burst_off]),
+ &st->data[offset]);
+ offset += 2;
+ } else {
+ /* All other channels are 24-bit stored as 32-bit */
+ msw = get_unaligned_be16(&fifo_data[burst_off]);
+ lsw = get_unaligned_be16(&fifo_data[burst_off + 2]);
+ val24 = sign_extend32(((u32)msw << 8) | (lsw >> 8), 23);
+ put_unaligned_be32(val24, &st->data[offset]);
+ offset += 4;
+ }
+ }
+
+ iio_push_to_buffers_with_ts(indio_dev, st->data, sizeof(st->data),
+ timestamp);
+}
+
+/*
+ * Drain the device FIFO. Triggered by the DEV_NOK interrupt, which fires on
+ * any DIAG_STAT flag, not just FIFO_THRESH_MET - the DIAG_STAT read below
+ * both checks which flag(s) raised the interrupt and clears the latched
+ * status, and must happen before any FIFO pop, or we'd pop for nothing on
+ * an unrelated flag.
+ */
+static void adis16607_fifo_drain(struct iio_dev *indio_dev, s64 irq_timestamp)
+{
+ struct adis16607 *st = iio_priv(indio_dev);
+ u8 fifo_sample[ADIS16607_FIFO_DATA_LEN];
+ u16 diag_stat, word_cnt, sample_cnt;
+ s64 timestamp;
+ int ret;
+
+ adis_dev_auto_lock(&st->adis);
+
+ /* Check if FIFO threshold is met */
+ ret = __adis_read_reg_16(&st->adis, ADIS16607_REG_DIAG_STAT, &diag_stat);
+ if (ret)
+ return;
+
+ if (!(diag_stat & ADIS16607_FIFO_THR_MET_MASK))
+ return;
+
+ /* Read FIFO word count */
+ ret = __adis_read_reg_16(&st->adis, ADIS16607_REG_FIFO_WORD_CNT, &word_cnt);
+ if (ret)
+ return;
+
+ /* Calculate number of complete samples */
+ sample_cnt = word_cnt / ADIS16607_FIFO_WORDS_PER_SAMPLE;
+ if (!sample_cnt)
+ return;
+
+ /*
+ * irq_timestamp is the IRQ-fire time, i.e. when the newest (last
+ * enqueued) sample tipped the FIFO over watermark. The FIFO pops
+ * oldest-first, so back-date the first popped sample's timestamp by
+ * the batch duration - the loop then counts back up to irq_timestamp
+ * for the newest sample, instead of projecting past it.
+ */
+ timestamp = irq_timestamp - (s64)(sample_cnt - 1) * st->sample_period_ns;
+
+ /* Pop and process each sample */
+ for (unsigned int i = 0; i < sample_cnt; i++) {
+ ret = adis16607_fifo_pop(st, fifo_sample);
+ if (ret)
+ return;
+
+ adis16607_fifo_push_sample(indio_dev, fifo_sample, timestamp);
+ timestamp += st->sample_period_ns;
+ }
+}
+
+static irqreturn_t adis16607_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+
+ adis16607_fifo_drain(indio_dev, pf->timestamp);
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+/* FIFO sysfs attributes */
+IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_min, "1");
+IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_max, __stringify(ADIS16607_MAX_FIFO_WM));
+
+static ssize_t hwfifo_watermark_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adis16607 *st = iio_priv(indio_dev);
+
+ return sysfs_emit(buf, "%u\n", st->fifo_watermark);
+}
+
+static ssize_t hwfifo_enabled_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adis16607 *st = iio_priv(indio_dev);
+ u16 val;
+ int ret;
+
+ ret = adis_read_reg_16(&st->adis, ADIS16607_REG_USER_FIFO_CFG, &val);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", (val & ADIS16607_FIFO_THR_MASK) ? 1 : 0);
+}
+
+static IIO_DEVICE_ATTR_RO(hwfifo_watermark, 0);
+static IIO_DEVICE_ATTR_RO(hwfifo_enabled, 0);
+
+static const struct iio_dev_attr *adis16607_fifo_attributes[] = {
+ &iio_dev_attr_hwfifo_watermark_min,
+ &iio_dev_attr_hwfifo_watermark_max,
+ &iio_dev_attr_hwfifo_watermark,
+ &iio_dev_attr_hwfifo_enabled,
+ NULL
+};
+
+static int adis16607_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct adis16607 *st = iio_priv(indio_dev);
+ struct adis *adis = &st->adis;
+ u16 threshold;
+ int ret;
+
+ adis_dev_auto_lock(adis);
+
+ /* Flush FIFO to start clean */
+ ret = __adis_write_reg_16(adis, ADIS16607_REG_USER_FIFO_CFG,
+ ADIS16607_FIFO_FLUSH_MASK);
+ if (ret)
+ return ret;
+
+ /* Enable FIFO with watermark threshold (in word count) */
+ threshold = st->fifo_watermark * ADIS16607_FIFO_WORDS_PER_SAMPLE;
+ return __adis_write_reg_16(adis, ADIS16607_REG_USER_FIFO_CFG,
+ threshold & ADIS16607_FIFO_THR_MASK);
+}
+
+static int adis16607_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct adis16607 *st = iio_priv(indio_dev);
+ struct adis *adis = &st->adis;
+ u16 diag_stat;
+ int ret;
+
+ adis_dev_auto_lock(adis);
+
+ /* Disable FIFO */
+ ret = __adis_write_reg_16(adis, ADIS16607_REG_USER_FIFO_CFG, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * Leave the device clean at the end of a session: the core attaches
+ * the trigger before calling postenable(), so the next session's
+ * handler could otherwise run on leftover samples and a still-latched
+ * FIFO_THR_MET.
+ */
+ ret = __adis_write_reg_16(adis, ADIS16607_REG_USER_FIFO_CFG,
+ ADIS16607_FIFO_FLUSH_MASK);
+ if (ret)
+ return ret;
+
+ return __adis_read_reg_16(adis, ADIS16607_REG_DIAG_STAT, &diag_stat);
+}
+
+static const struct iio_buffer_setup_ops adis16607_buffer_ops = {
+ .postenable = adis16607_buffer_postenable,
+ .postdisable = adis16607_buffer_postdisable,
+};
+
+static int adis16607_set_watermark(struct iio_dev *indio_dev, unsigned int val)
+{
+ struct adis16607 *st = iio_priv(indio_dev);
+
+ val = clamp(val, 1, ADIS16607_MAX_FIFO_WM);
+ st->fifo_watermark = val;
+
+ return 0;
+}
+
/*
* Check if the delta between two values is within specified range.
*/
@@ -316,7 +648,8 @@ static int adis16607_sensor_self_test(struct adis16607 *st)
/*
* Called by __adis_initial_startup() right after reset, before self-test:
- * SPI Half-Duplex locking, dev_id verification and the bootloader check.
+ * SPI Half-Duplex locking, dev_id verification, the bootloader check,
+ * enabling 32-bit burst mode + DATA_CNTR and the DR/DEV_NOK pin routing.
*/
static int adis16607_init(struct adis *adis)
{
@@ -357,7 +690,29 @@ static int adis16607_init(struct adis *adis)
if (digital_status & ADIS16607_BOOTLOADER_BUSY_MASK)
return dev_err_probe(dev, -EBUSY, "bootloader is busy\n");
- return 0;
+ /* Enable 32-bit burst mode and DATA_CNTR - one-time, used by FIFO pops */
+ ret = __adis_update_bits_base(adis, ADIS16607_REG_USER_DATA_CFG,
+ ADIS16607_BURST32_MASK | ADIS16607_DATA_CNTR_EN_MASK,
+ ADIS16607_BURST32_MASK | ADIS16607_DATA_CNTR_EN_MASK, 2);
+ if (ret)
+ return ret;
+
+ /*
+ * DR_GPIO_MASK and DEV_NOK_MASK are updated here rather than written
+ * blind, so this doesn't clobber the RESET_GPIO_MASK bit already
+ * configured earlier in probe(). Per datasheet, DR pulses are what
+ * drive the FIFO controller's internal enqueue logic. DR and
+ * DEV_NOK are separate physical pins, so enabling DR_GPIO_MASK here
+ * does not put DR's full-sample-rate pulses onto the interrupt line
+ * - DEV_NOK_MASK routes the aggregate "any DIAG_STAT flag asserted"
+ * signal onto the one physically-wired interrupt pin, which is what
+ * actually fires on FIFO_THRESH_MET, at the (much lower) watermark
+ * rate, not the sample rate.
+ */
+ return __adis_update_bits_base(adis, ADIS16607_REG_USER_GPIO_CFG,
+ ADIS16607_DR_GPIO_MASK | ADIS16607_DEV_NOK_MASK,
+ FIELD_PREP(ADIS16607_DR_GPIO_MASK, 1) |
+ FIELD_PREP(ADIS16607_DEV_NOK_MASK, 1), 2);
}
/*
@@ -609,7 +964,12 @@ static int adis16607_write_raw(struct iio_dev *indio_dev,
u32 tmp;
switch (info) {
- case IIO_CHAN_INFO_SAMP_FREQ:
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ /* The decimation must not change while the buffer is running */
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
if (val < 0 || val2 < 0)
return -EINVAL;
@@ -617,7 +977,7 @@ static int adis16607_write_raw(struct iio_dev *indio_dev,
tmp = val * MILLIHZ_PER_HZ +
val2 / (MICROHZ_PER_HZ / MILLIHZ_PER_HZ);
return adis16607_set_freq(st, tmp);
-
+ }
default:
return -EINVAL;
}
@@ -716,6 +1076,7 @@ static const struct iio_info adis16607_info = {
.read_raw = adis16607_read_raw,
.write_raw = adis16607_write_raw,
.debugfs_reg_access = adis_debugfs_reg_access,
+ .hwfifo_set_watermark = adis16607_set_watermark,
};
static const char * const adis16607_status_error_msgs[] = {
@@ -737,6 +1098,7 @@ static const struct adis_data adis16607_data = {
.prod_id_reg = ADIS16607_REG_DEV_ID,
.prod_id = 0x6000,
.unmasked_drdy = true,
+ .has_fifo = true,
.init = adis16607_init,
.self_test = adis16607_self_test,
.status_error_msgs = adis16607_status_error_msgs,
@@ -800,6 +1162,7 @@ static int adis16607_probe(struct spi_device *spi)
struct device *dev = &spi->dev;
struct iio_dev *indio_dev;
struct adis16607 *st;
+ u32 irq_type;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
@@ -817,6 +1180,8 @@ static int adis16607_probe(struct spi_device *spi)
indio_dev->info = &adis16607_info;
indio_dev->modes = INDIO_DIRECT_MODE;
+ st->fifo_watermark = ADIS16607_MAX_FIFO_WM;
+
st->adis.ops = &adis16607_ops;
ret = devm_regulator_get_enable(dev, "vdd");
@@ -855,6 +1220,25 @@ static int adis16607_probe(struct spi_device *spi)
if (ret)
return ret;
+ /*
+ * DEV_NOK stays asserted until DIAG_STAT is read, so the interrupt
+ * must be level-triggered, active high.
+ */
+ irq_type = irq_get_trigger_type(spi->irq);
+ if (irq_type != IRQ_TYPE_LEVEL_HIGH)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid interrupt type 0x%x specified\n",
+ irq_type);
+
+ st->adis.irq_flag = IRQF_TRIGGER_HIGH;
+
+ ret = devm_adis_setup_buffer_and_trigger_with_attrs(&st->adis, indio_dev,
+ adis16607_trigger_handler,
+ &adis16607_buffer_ops,
+ adis16607_fifo_attributes);
+ if (ret)
+ return ret;
+
ret = devm_iio_device_register(dev, indio_dev);
if (ret)
return ret;
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 7/8] iio: imu: adis16607: Add external clock sync support
2026-11-02 9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
` (5 preceding siblings ...)
2026-11-02 9:25 ` [PATCH v5 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture Radu Sabau via B4 Relay
@ 2026-11-02 9:25 ` Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel Radu Sabau via B4 Relay
7 siblings, 0 replies; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02 9:25 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron
From: Radu Sabau <radu.sabau@analog.com>
Add support for an optional external clock (clocks property),
selecting between SYNC_DIRECT (1 kHz to 8 kHz) and SYNC_SCALED (1 Hz
to 400 Hz) depending on the supplied frequency, with the internal
8 kHz clock used as the default when no clock is provided.
The clock rate is requested as exclusive since the sample period and
the sync scale are derived from it. In scaled mode the sync scale is
chosen when the sampling frequency is set, so that the requested rate
is matched as closely as possible while keeping the clock multiplied
at or below the internal rate. Loss of sync or of the PLL lock is
reported from the trigger handler, as the device keeps sampling
without it.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/imu/adis16607.c | 219 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 206 insertions(+), 13 deletions(-)
diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
index bd7752f7a1ec..58504d351de1 100644
--- a/drivers/iio/imu/adis16607.c
+++ b/drivers/iio/imu/adis16607.c
@@ -8,6 +8,7 @@
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/build_bug.h>
+#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/iio/buffer.h>
@@ -50,6 +51,7 @@
#define ADIS16607_REG_FIFO_WORD_CNT 0x2B
#define ADIS16607_REG_USER_GPIO_CFG 0x2F
#define ADIS16607_REG_SPI_HALFDUPLEX 0x32
+#define ADIS16607_REG_USER_SYNC 0x33
#define ADIS16607_REG_USER_DATA_CFG 0x34
#define ADIS16607_REG_USER_FIFO_CFG 0x35
#define ADIS16607_REG_SOFT_RESET 0x36
@@ -62,12 +64,18 @@
/* Masks */
#define ADIS16607_RESET_GPIO_MASK GENMASK(2, 0)
+#define ADIS16607_SYNC_GPIO_MASK GENMASK(8, 6)
#define ADIS16607_DR_GPIO_MASK GENMASK(11, 9)
#define ADIS16607_DEV_NOK_MASK GENMASK(14, 12)
#define ADIS16607_DATA_CNTR_EN_MASK BIT(14)
#define ADIS16607_BURST32_MASK BIT(15)
+#define ADIS16607_SYNC_MODE_MASK BIT(15)
#define ADIS16607_BOOTLOADER_BUSY_MASK BIT(0)
+/* DIAG_STAT sync flags */
+#define ADIS16607_DIAG_DPLL_UNLOCKED BIT(1)
+#define ADIS16607_DIAG_SYNC_LOST BIT(2)
+
/* FIFO masks */
#define ADIS16607_FIFO_THR_MASK GENMASK(10, 0)
#define ADIS16607_FIFO_FLUSH_MASK BIT(15)
@@ -95,8 +103,22 @@
#define ADIS16607_ACCEL_Z_DELTA_MAX 4000
#define ADIS16607_GYRO_DELTA_MAX 2600
+/* Sync modes */
+enum adis16607_sync_mode {
+ ADIS16607_SYNC_DEFAULT,
+ ADIS16607_SYNC_DIRECT,
+ ADIS16607_SYNC_SCALED,
+};
+
+struct adis16607_sync {
+ u16 sync_mode;
+ u16 min_rate;
+ u16 max_rate;
+};
+
struct adis16607_chip_info {
const struct iio_chan_spec *channels;
+ const struct adis16607_sync *sync_mode;
const char *name;
u32 num_channels;
u32 gyro_max_val;
@@ -108,6 +130,7 @@ struct adis16607_chip_info {
u32 deltvel_max_val;
u32 int_clk_Hz;
u16 max_dec;
+ u8 num_sync;
};
struct adis16607 {
@@ -115,6 +138,9 @@ struct adis16607 {
const struct adis16607_chip_info *info;
unsigned long clk_freq_mHz;
u64 sample_period_ns;
+ unsigned long ext_clk_Hz;
+ u32 sync_mode;
+ u16 sync_scale;
u16 fifo_watermark;
/* IIO buffer for reordered data - sized for max channels */
IIO_DECLARE_BUFFER_WITH_TS(u8, data, ADIS16607_SCAN_DATA_SIZE);
@@ -407,11 +433,18 @@ static void adis16607_fifo_drain(struct iio_dev *indio_dev, s64 irq_timestamp)
adis_dev_auto_lock(&st->adis);
- /* Check if FIFO threshold is met */
ret = __adis_read_reg_16(&st->adis, ADIS16607_REG_DIAG_STAT, &diag_stat);
if (ret)
return;
+ /* The device keeps sampling without sync, so only warn about it */
+ if (diag_stat & ADIS16607_DIAG_SYNC_LOST)
+ dev_warn_ratelimited(&st->adis.spi->dev, "external sync lost\n");
+
+ if (diag_stat & ADIS16607_DIAG_DPLL_UNLOCKED)
+ dev_warn_ratelimited(&st->adis.spi->dev, "sync PLL unlocked\n");
+
+ /* Check if FIFO threshold is met */
if (!(diag_stat & ADIS16607_FIFO_THR_MET_MASK))
return;
@@ -841,8 +874,44 @@ static int adis16607_get_freq(struct adis16607 *st, u32 *freq)
return 0;
}
+/*
+ * In scaled sync mode the output rate is ext_clk * scale / (dec + 1). Find the
+ * (scale, dec) pair closest to the requested rate (in mHz), keeping
+ * ext_clk * scale at or below the internal rate. On a tie the higher scale
+ * wins, so the decimation filter averages more samples.
+ */
+static void adis16607_scaled_params(struct adis16607 *st, u32 freq,
+ u16 *scale, u32 *dec)
+{
+ u32 max_scale = st->info->int_clk_Hz / st->ext_clk_Hz;
+ u32 max_dec = st->info->max_dec + 1;
+ u64 best_err = U64_MAX;
+ u64 target = freq;
+
+ for (u32 k = 1; k <= max_scale; k++) {
+ u64 rate = st->ext_clk_Hz;
+ u64 div, odr, err;
+
+ rate *= k;
+ rate *= MILLIHZ_PER_HZ;
+
+ div = DIV_ROUND_CLOSEST_ULL(rate, freq);
+ div = clamp(div, 1, max_dec);
+ odr = DIV_ROUND_CLOSEST_ULL(rate, div);
+ err = abs_diff(odr, target);
+ if (err > best_err)
+ continue;
+
+ best_err = err;
+ *scale = k;
+ *dec = div - 1;
+ }
+}
+
static int adis16607_set_freq(struct adis16607 *st, u32 freq)
{
+ unsigned long clk_freq_mHz = st->clk_freq_mHz;
+ u16 scale = st->sync_scale;
u64 period_ns;
u32 dec;
int ret;
@@ -850,11 +919,16 @@ static int adis16607_set_freq(struct adis16607 *st, u32 freq)
if (!freq)
return -EINVAL;
- dec = DIV_ROUND_CLOSEST(st->clk_freq_mHz, freq);
- if (dec)
- dec--;
+ if (st->sync_mode == ADIS16607_SYNC_SCALED) {
+ adis16607_scaled_params(st, freq, &scale, &dec);
+ clk_freq_mHz = st->ext_clk_Hz * scale * MILLIHZ_PER_HZ;
+ } else {
+ dec = DIV_ROUND_CLOSEST(clk_freq_mHz, freq);
+ if (dec)
+ dec--;
- dec = min(dec, st->info->max_dec);
+ dec = min(dec, st->info->max_dec);
+ }
/*
* The sample period is (dec + 1) / clk_freq. clk_freq_mHz is in
@@ -865,7 +939,7 @@ static int adis16607_set_freq(struct adis16607 *st, u32 freq)
period_ns = NSEC_PER_SEC;
period_ns *= MILLIHZ_PER_HZ;
period_ns *= dec + 1;
- period_ns = DIV_ROUND_CLOSEST_ULL(period_ns, st->clk_freq_mHz);
+ period_ns = DIV_ROUND_CLOSEST_ULL(period_ns, clk_freq_mHz);
adis_dev_auto_scoped_lock(&st->adis) {
/* Reset the decimation accumulator before the new value */
@@ -873,6 +947,21 @@ static int adis16607_set_freq(struct adis16607 *st, u32 freq)
if (ret)
return ret;
+ /*
+ * Writing USER_SYNC restarts the PLL lock, so only do it when
+ * the scale actually changes.
+ */
+ if (scale != st->sync_scale) {
+ ret = __adis_write_reg_16(&st->adis,
+ ADIS16607_REG_USER_SYNC,
+ ADIS16607_SYNC_MODE_MASK | scale);
+ if (ret)
+ return ret;
+
+ st->sync_scale = scale;
+ st->clk_freq_mHz = clk_freq_mHz;
+ }
+
if (dec) {
ret = __adis_write_reg_16(&st->adis,
ADIS16607_REG_DEC_RATE, dec);
@@ -1079,6 +1168,17 @@ static const struct iio_info adis16607_info = {
.hwfifo_set_watermark = adis16607_set_watermark,
};
+/*
+ * External sync clock ranges in Hz. The datasheet gives 0.8 Hz as the scaled
+ * mode minimum, but clk_get_rate() reports whole Hz so 1 Hz is the lowest
+ * rate we can match.
+ */
+static const struct adis16607_sync adis16607_sync_modes[] = {
+ { ADIS16607_SYNC_DEFAULT, 0, 0 },
+ { ADIS16607_SYNC_DIRECT, 1000, 8000 },
+ { ADIS16607_SYNC_SCALED, 1, 400 },
+};
+
static const char * const adis16607_status_error_msgs[] = {
[9] = "Boot Memory Failure",
[11] = "Power Supply Failure",
@@ -1123,6 +1223,8 @@ static const struct adis16607_chip_info adis16607_2_chip_info = {
.deltvel_max_val = IIO_G_TO_M_S_2(40),
.int_clk_Hz = 8000,
.max_dec = 65535,
+ .sync_mode = adis16607_sync_modes,
+ .num_sync = ARRAY_SIZE(adis16607_sync_modes),
};
static const struct adis16607_chip_info adis16607_3_chip_info = {
@@ -1138,22 +1240,113 @@ static const struct adis16607_chip_info adis16607_3_chip_info = {
.deltvel_max_val = IIO_G_TO_M_S_2(40),
.int_clk_Hz = 8000,
.max_dec = 65535,
+ .sync_mode = adis16607_sync_modes,
+ .num_sync = ARRAY_SIZE(adis16607_sync_modes),
};
-/*
- * No external clock sync support yet: always run on the internal clock.
- * Default dec=0 after reset, so period = 1 / clk_freq_mHz.
- */
static int adis16607_config_clk(struct adis16607 *st)
{
- u64 period_ns = NSEC_PER_SEC;
+ const struct adis16607_sync *sync_mode_data = NULL;
+ struct device *dev = &st->adis.spi->dev;
+ struct clk *clk;
+ u64 period_ns;
+ u16 dec;
+ int ret;
+
+ clk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ if (clk) {
+ /* The sync scale and sample period depend on this rate */
+ ret = devm_clk_rate_exclusive_get(dev, clk);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get exclusive clk rate\n");
+
+ st->ext_clk_Hz = clk_get_rate(clk);
+
+ for (unsigned int i = 1; i < st->info->num_sync; i++) {
+ if (st->ext_clk_Hz >= st->info->sync_mode[i].min_rate &&
+ st->ext_clk_Hz <= st->info->sync_mode[i].max_rate) {
+ sync_mode_data = &st->info->sync_mode[i];
+ break;
+ }
+ }
+
+ if (!sync_mode_data)
+ return dev_err_probe(dev, -EINVAL,
+ "Clk rate: %lu not in a valid range\n",
+ st->ext_clk_Hz);
+
+ st->sync_mode = sync_mode_data->sync_mode;
+ } else {
+ st->sync_mode = ADIS16607_SYNC_DEFAULT;
+ }
+
+ /*
+ * Write the full desired sync state explicitly for every mode,
+ * rather than only when enabling non-default sync: USER_SYNC's
+ * documented reset default (0x0000, direct mode / scale 0) means
+ * this isn't fixing a known reset-state issue, but each mode
+ * establishing its own complete state here, instead of only
+ * touching bits conditionally, is simpler to reason about and
+ * doesn't depend on that default holding true.
+ */
+ ret = adis_update_bits_base(&st->adis, ADIS16607_REG_USER_GPIO_CFG,
+ ADIS16607_SYNC_GPIO_MASK,
+ FIELD_PREP(ADIS16607_SYNC_GPIO_MASK,
+ st->sync_mode != ADIS16607_SYNC_DEFAULT), 2);
+ if (ret)
+ return ret;
+
+ /*
+ * Start at the highest scale that keeps ext_clk * scale at or below
+ * the internal rate, which the datasheet says the part performs best
+ * with. set_freq() picks the scale from then on.
+ */
+ if (st->sync_mode == ADIS16607_SYNC_SCALED)
+ st->sync_scale = st->info->int_clk_Hz / st->ext_clk_Hz;
- st->clk_freq_mHz = st->info->int_clk_Hz * MILLIHZ_PER_HZ;
+ ret = adis_write_reg_16(&st->adis, ADIS16607_REG_USER_SYNC,
+ (st->sync_mode == ADIS16607_SYNC_SCALED) ?
+ ADIS16607_SYNC_MODE_MASK | st->sync_scale : 0);
+ if (ret)
+ return ret;
- /* clk_freq_mHz is in millihertz: scale to get the period in ns */
+ switch (st->sync_mode) {
+ case ADIS16607_SYNC_SCALED:
+ /*
+ * The effective rate is ext_clk * sync_scale, not necessarily
+ * int_clk_Hz - use the real value here so sample_period_ns (and
+ * therefore FIFO sample timestamps) don't drift from it.
+ */
+ st->clk_freq_mHz = st->ext_clk_Hz * st->sync_scale *
+ MILLIHZ_PER_HZ;
+ break;
+ case ADIS16607_SYNC_DIRECT:
+ /* DIRECT: external clock is the effective internal rate */
+ st->clk_freq_mHz = st->ext_clk_Hz * MILLIHZ_PER_HZ;
+ break;
+ default:
+ st->clk_freq_mHz = st->info->int_clk_Hz * MILLIHZ_PER_HZ;
+ break;
+ }
+
+ ret = adis_read_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, &dec);
+ if (ret)
+ return ret;
+
+ /*
+ * Sample period is (dec + 1) / clk_freq, with clk_freq_mHz in
+ * millihertz. Same u64-first scheme as adis16607_set_freq().
+ */
+ period_ns = NSEC_PER_SEC;
period_ns *= MILLIHZ_PER_HZ;
+ period_ns *= dec + 1;
st->sample_period_ns = DIV_ROUND_CLOSEST_ULL(period_ns,
st->clk_freq_mHz);
+
return 0;
}
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel
2026-11-02 9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
` (6 preceding siblings ...)
2026-11-02 9:25 ` [PATCH v5 7/8] iio: imu: adis16607: Add external clock sync support Radu Sabau via B4 Relay
@ 2026-11-02 9:25 ` Radu Sabau via B4 Relay
7 siblings, 0 replies; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-02 9:25 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Nuno Sa, Jonathan Cameron,
David Lechner, Andy Shevchenko, Alexandru Ardelean, Radu Sabau,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron
From: Radu Sabau <radu.sabau@analog.com>
Add IIO_CHAN_INFO_CALIBBIAS support for the gyroscope and
accelerometer axes, backed by the device's per-axis bias registers
(XA/YA/ZA_BIAS, XG/YG/ZG_BIAS).
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/imu/adis16607.c | 39 ++++++++++++++++++++++++++++++++-------
1 file changed, 32 insertions(+), 7 deletions(-)
diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
index 58504d351de1..ab80e0909bf6 100644
--- a/drivers/iio/imu/adis16607.c
+++ b/drivers/iio/imu/adis16607.c
@@ -57,6 +57,12 @@
#define ADIS16607_REG_SOFT_RESET 0x36
#define ADIS16607_REG_SELF_TEST 0x39
#define ADIS16607_REG_DEC_RATE 0x3A
+#define ADIS16607_REG_XA_BIAS 0x3B
+#define ADIS16607_REG_YA_BIAS 0x3C
+#define ADIS16607_REG_ZA_BIAS 0x3D
+#define ADIS16607_REG_XG_BIAS 0x3E
+#define ADIS16607_REG_YG_BIAS 0x3F
+#define ADIS16607_REG_ZG_BIAS 0x40
#define ADIS16607_REG_DIGITAL_STATUS 0x4E
/* SPI Half-Duplex lock value */
@@ -168,15 +174,16 @@ enum {
struct adis16607_chan_regs {
u16 raw_reg;
+ u16 calib_bias_reg; /* 0 if the channel has no calibbias support */
};
static const struct adis16607_chan_regs adis16607_chan_regs[] = {
- [ADIS16607_SCAN_ACCEL_X] = { ADIS16607_REG_X_ACCEL },
- [ADIS16607_SCAN_ACCEL_Y] = { ADIS16607_REG_Y_ACCEL },
- [ADIS16607_SCAN_ACCEL_Z] = { ADIS16607_REG_Z_ACCEL },
- [ADIS16607_SCAN_GYRO_X] = { ADIS16607_REG_X_GYRO },
- [ADIS16607_SCAN_GYRO_Y] = { ADIS16607_REG_Y_GYRO },
- [ADIS16607_SCAN_GYRO_Z] = { ADIS16607_REG_Z_GYRO },
+ [ADIS16607_SCAN_ACCEL_X] = { ADIS16607_REG_X_ACCEL, ADIS16607_REG_XA_BIAS },
+ [ADIS16607_SCAN_ACCEL_Y] = { ADIS16607_REG_Y_ACCEL, ADIS16607_REG_YA_BIAS },
+ [ADIS16607_SCAN_ACCEL_Z] = { ADIS16607_REG_Z_ACCEL, ADIS16607_REG_ZA_BIAS },
+ [ADIS16607_SCAN_GYRO_X] = { ADIS16607_REG_X_GYRO, ADIS16607_REG_XG_BIAS },
+ [ADIS16607_SCAN_GYRO_Y] = { ADIS16607_REG_Y_GYRO, ADIS16607_REG_YG_BIAS },
+ [ADIS16607_SCAN_GYRO_Z] = { ADIS16607_REG_Z_GYRO, ADIS16607_REG_ZG_BIAS },
[ADIS16607_SCAN_DELTVEL_X] = { ADIS16607_REG_X_DELTVEL },
[ADIS16607_SCAN_DELTVEL_Y] = { ADIS16607_REG_Y_DELTVEL },
[ADIS16607_SCAN_DELTVEL_Z] = { ADIS16607_REG_Z_DELTVEL },
@@ -1030,6 +1037,16 @@ static int adis16607_read_raw(struct iio_dev *indio_dev,
/* Temperature: 0 raw counts = 25°C; offset = 25000 mdeg / 5 mdeg per LSB = 5000 */
*val = 5000;
return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CALIBBIAS: {
+ u16 reg = adis16607_chan_regs[chan->address].calib_bias_reg;
+ u16 tmp16;
+
+ ret = adis_read_reg_16(&st->adis, reg, &tmp16);
+ if (ret)
+ return ret;
+ *val = sign_extend32(tmp16, 15);
+ return IIO_VAL_INT;
+ }
case IIO_CHAN_INFO_SAMP_FREQ:
ret = adis16607_get_freq(st, &tmp);
if (ret)
@@ -1067,6 +1084,13 @@ static int adis16607_write_raw(struct iio_dev *indio_dev,
val2 / (MICROHZ_PER_HZ / MILLIHZ_PER_HZ);
return adis16607_set_freq(st, tmp);
}
+ case IIO_CHAN_INFO_CALIBBIAS: {
+ u16 reg = adis16607_chan_regs[chan->address].calib_bias_reg;
+
+ if (val < S16_MIN || val > S16_MAX)
+ return -EINVAL;
+ return adis_write_reg_16(&st->adis, reg, val);
+ }
default:
return -EINVAL;
}
@@ -1081,7 +1105,8 @@ static int adis16607_write_raw(struct iio_dev *indio_dev,
.type = (_type), \
.modified = 1, \
.channel2 = (_mod), \
- .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
.address = (_idx), \
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-11-02 9:25 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-02 9:25 [PATCH v5 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 1/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 2/8] iio: imu: adis: Add optional self_test callback Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 3/8] iio: imu: adis: Add optional init callback Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607 Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 5/8] iio: imu: Add driver for the " Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 7/8] iio: imu: adis16607: Add external clock sync support Radu Sabau via B4 Relay
2026-11-02 9:25 ` [PATCH v5 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel Radu Sabau via B4 Relay
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®