* [PATCH v6 1/8] iio: imu: adis: Fix custom reset dispatch in adislib
2026-11-13 12:33 [PATCH v6 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
@ 2026-11-13 12:33 ` Radu Sabau via B4 Relay
2026-11-13 12:33 ` [PATCH v6 2/8] iio: imu: adis: Add optional self_test callback Radu Sabau via B4 Relay
` (7 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-13 12:33 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, Antoniu Miclaus,
Robert Budai, Ramona Gradinariu
Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron
From: Radu Sabau <radu.sabau@analog.com>
Commit 7f15d7a7d12d ("iio: imu: adis: Add reset to custom ops") added
a reset callback to struct adis_ops, but __adis_initial_startup()'s
no-reset-gpio path and the public adis_reset() wrapper kept calling
__adis_reset() directly, so a driver's custom reset implementation is
silently ignored there (adis16550 already provides one). Dispatch
through adis->ops->reset() instead.
__adis_reset() also did the settle-time sleep itself, which a custom op
never reaches even though its kernel-doc says the library takes care of
the delay. Move the sleep out of __adis_reset() and into both call
sites so it follows whichever reset op is used.
Fixes: 7f15d7a7d12d ("iio: imu: adis: Add reset to custom ops")
Reviewed-by: Nuno Sá <nuno.sa@analog.com>
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/imu/adis.c | 6 ++----
include/linux/iio/imu/adis.h | 12 +++++++++++-
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c
index a2bc1d14ed91..793e3b6b0ea7 100644
--- a/drivers/iio/imu/adis.c
+++ b/drivers/iio/imu/adis.c
@@ -340,7 +340,6 @@ EXPORT_SYMBOL_NS_GPL(__adis_check_status, "IIO_ADISLIB");
int __adis_reset(struct adis *adis)
{
int ret;
- const struct adis_timeout *timeouts = adis->data->timeouts;
ret = __adis_write_reg_8(adis, adis->data->glob_cmd_reg,
ADIS_GLOB_CMD_SW_RESET);
@@ -349,8 +348,6 @@ int __adis_reset(struct adis *adis)
return ret;
}
- msleep(timeouts->sw_reset_ms);
-
return 0;
}
EXPORT_SYMBOL_NS_GPL(__adis_reset, "IIO_ADIS_LIB");
@@ -413,9 +410,10 @@ int __adis_initial_startup(struct adis *adis)
gpiod_set_value_cansleep(gpio, 0);
msleep(timeouts->reset_ms);
} else {
- ret = __adis_reset(adis);
+ ret = adis->ops->reset(adis);
if (ret)
return ret;
+ msleep(timeouts->sw_reset_ms);
}
ret = adis_self_test(adis);
diff --git a/include/linux/iio/imu/adis.h b/include/linux/iio/imu/adis.h
index bfb6df68e6c9..97323b8d1cb3 100644
--- a/include/linux/iio/imu/adis.h
+++ b/include/linux/iio/imu/adis.h
@@ -10,6 +10,7 @@
#define __IIO_ADIS_H__
#include <linux/cleanup.h>
+#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <linux/interrupt.h>
#include <linux/iio/iio.h>
@@ -171,8 +172,17 @@ int __adis_reset(struct adis *adis);
*/
static inline int adis_reset(struct adis *adis)
{
+ int ret;
+
guard(mutex)(&adis->state_lock);
- return __adis_reset(adis);
+
+ ret = adis->ops->reset(adis);
+ if (ret)
+ return ret;
+
+ msleep(adis->data->timeouts->sw_reset_ms);
+
+ return 0;
}
int __adis_write_reg(struct adis *adis, unsigned int reg,
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v6 2/8] iio: imu: adis: Add optional self_test callback
2026-11-13 12:33 [PATCH v6 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
2026-11-13 12:33 ` [PATCH v6 1/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
@ 2026-11-13 12:33 ` Radu Sabau via B4 Relay
2026-11-13 12:33 ` [PATCH v6 3/8] iio: imu: adis: Add optional init callback Radu Sabau via B4 Relay
` (6 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-13 12:33 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, Antoniu Miclaus,
Robert Budai, Ramona Gradinariu
Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron
From: Radu Sabau <radu.sabau@analog.com>
Add an optional self_test callback to struct adis_data, for devices
whose self-test doesn't fit the generic mask-write/sleep/check-status
sequence. Purely additive: defaults to NULL, no change for existing
drivers.
Reviewed-by: Nuno Sá <nuno.sa@analog.com>
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/imu/adis.c | 3 +++
include/linux/iio/imu/adis.h | 7 +++++++
2 files changed, 10 insertions(+)
diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c
index 793e3b6b0ea7..5c3bb7259037 100644
--- a/drivers/iio/imu/adis.c
+++ b/drivers/iio/imu/adis.c
@@ -357,6 +357,9 @@ static int adis_self_test(struct adis *adis)
int ret;
const struct adis_timeout *timeouts = adis->data->timeouts;
+ if (adis->data->self_test)
+ return adis->data->self_test(adis);
+
ret = __adis_write_reg_16(adis, adis->data->self_test_reg,
adis->data->self_test_mask);
if (ret) {
diff --git a/include/linux/iio/imu/adis.h b/include/linux/iio/imu/adis.h
index 97323b8d1cb3..c09b381eb619 100644
--- a/include/linux/iio/imu/adis.h
+++ b/include/linux/iio/imu/adis.h
@@ -52,6 +52,12 @@ struct adis_timeout {
* @self_test_mask: Bitmask of supported self-test operations
* @self_test_reg: Register address to request self test command
* @self_test_no_autoclear: True if device's self-test needs clear of ctrl reg
+ * @self_test: Optional device-specific self-test override. When set,
+ * it is called instead of the generic mask-write/sleep/
+ * check-status sequence. The callback owns its own pass/fail
+ * determination entirely (it should call __adis_check_status()
+ * itself if it wants status-register checks). Must return 0 on
+ * success, a negative error code otherwise.
* @status_error_msgs: Array of error messages
* @status_error_mask: Bitmask of errors supported by the device
* @timeouts: Chip specific delays
@@ -82,6 +88,7 @@ struct adis_data {
unsigned int self_test_mask;
unsigned int self_test_reg;
bool self_test_no_autoclear;
+ int (*self_test)(struct adis *adis);
const struct adis_timeout *timeouts;
const char * const *status_error_msgs;
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v6 3/8] iio: imu: adis: Add optional init callback
2026-11-13 12:33 [PATCH v6 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
2026-11-13 12:33 ` [PATCH v6 1/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
2026-11-13 12:33 ` [PATCH v6 2/8] iio: imu: adis: Add optional self_test callback Radu Sabau via B4 Relay
@ 2026-11-13 12:33 ` Radu Sabau via B4 Relay
2026-11-13 12:33 ` [PATCH v6 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607 Radu Sabau via B4 Relay
` (5 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-13 12:33 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, Antoniu Miclaus,
Robert Budai, Ramona Gradinariu
Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron
From: Radu Sabau <radu.sabau@analog.com>
Add an optional init callback to struct adis_data, called right
after reset and before self-test. Not all ADIS devices need extra
bring-up between reset and self-test (SPI mode locking, status or
bootloader checks, pin or burst-mode configuration, etc.) - purely
additive: defaults to NULL, no change for existing drivers.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/imu/adis.c | 6 ++++++
include/linux/iio/imu/adis.h | 7 +++++++
2 files changed, 13 insertions(+)
diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c
index 5c3bb7259037..e2cf42b8b946 100644
--- a/drivers/iio/imu/adis.c
+++ b/drivers/iio/imu/adis.c
@@ -419,6 +419,12 @@ int __adis_initial_startup(struct adis *adis)
msleep(timeouts->sw_reset_ms);
}
+ if (adis->data->init) {
+ ret = adis->data->init(adis);
+ if (ret)
+ return ret;
+ }
+
ret = adis_self_test(adis);
if (ret)
return ret;
diff --git a/include/linux/iio/imu/adis.h b/include/linux/iio/imu/adis.h
index c09b381eb619..c9016f636530 100644
--- a/include/linux/iio/imu/adis.h
+++ b/include/linux/iio/imu/adis.h
@@ -52,6 +52,12 @@ struct adis_timeout {
* @self_test_mask: Bitmask of supported self-test operations
* @self_test_reg: Register address to request self test command
* @self_test_no_autoclear: True if device's self-test needs clear of ctrl reg
+ * @init: Optional device-specific hook called after reset,
+ * before self-test. Not all ADIS devices need extra bring-up
+ * between reset and self-test (SPI mode locking, status or
+ * bootloader checks, pin or burst-mode configuration, etc.) -
+ * only called when set. Must return 0 on success, a negative
+ * error code otherwise.
* @self_test: Optional device-specific self-test override. When set,
* it is called instead of the generic mask-write/sleep/
* check-status sequence. The callback owns its own pass/fail
@@ -88,6 +94,7 @@ struct adis_data {
unsigned int self_test_mask;
unsigned int self_test_reg;
bool self_test_no_autoclear;
+ int (*init)(struct adis *adis);
int (*self_test)(struct adis *adis);
const struct adis_timeout *timeouts;
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v6 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607
2026-11-13 12:33 [PATCH v6 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
` (2 preceding siblings ...)
2026-11-13 12:33 ` [PATCH v6 3/8] iio: imu: adis: Add optional init callback Radu Sabau via B4 Relay
@ 2026-11-13 12:33 ` Radu Sabau via B4 Relay
2026-11-13 12:33 ` [PATCH v6 5/8] iio: imu: Add driver for the " Radu Sabau via B4 Relay
` (4 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-13 12:33 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, Antoniu Miclaus,
Robert Budai, Ramona Gradinariu
Cc: linux-iio, linux-kernel, devicetree, linux, Jonathan Cameron,
Conor Dooley
From: Radu Sabau <radu.sabau@analog.com>
Add device tree bindings for the Analog Devices ADIS16607 Precision
MEMS Inertial Measurement Unit. The ADIS16607 integrates a triaxial
gyroscope, triaxial accelerometer, and temperature sensor, and
communicates over SPI Half-Duplex.
Two variants are supported, adis16607-2 and adis16607-3, differing
only in gyroscope full-scale range (±450°/s and ±2000°/s
respectively).
The binding exposes optional properties for:
- An external clock for sync mode (falling back to the internal 8 kHz
clock when absent)
- A hardware reset GPIO (active low)
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
.../devicetree/bindings/iio/imu/adi,adis16607.yaml | 94 ++++++++++++++++++++++
MAINTAINERS | 7 ++
2 files changed, 101 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml b/Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
new file mode 100644
index 000000000000..4545f942365c
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
@@ -0,0 +1,94 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/imu/adi,adis16607.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices ADIS16607 IMU
+
+maintainers:
+ - Radu Sabau <radu.sabau@analog.com>
+
+description: |
+ Analog Devices ADIS16607 Precision MEMS Inertial Measurement Unit (IMU)
+ with a triaxial gyroscope, triaxial accelerometer, and temperature sensor.
+ Uses SPI Half-Duplex communication protocol.
+
+ The device has five GPIO pins (GPIO0-GPIO4 in the datasheet), each
+ independently selectable for a single fixed function. This binding
+ exposes GPIO0's reset function as reset-gpios; GPIO2's sync function
+ is modeled via the clocks property, and GPIO4's DEV_NOK interrupt via
+ the interrupts property. GPIO3 (data-ready) is required internally
+ regardless of board wiring and isn't exposed. GPIO1 (wake) gates
+ whether the bootloader auto-loads its NVM contents after reset; it
+ defaults to enabled with a weak pull-up, which already gives normal
+ operation with no configuration needed, so it isn't exposed either.
+
+properties:
+ compatible:
+ enum:
+ - adi,adis16607-2
+ - adi,adis16607-3
+
+ reg:
+ maxItems: 1
+
+ spi-cpha: true
+
+ spi-cpol: true
+
+ spi-max-frequency:
+ maximum: 10000000
+
+ vdd-supply: true
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ description:
+ Optional external clock for sync mode. If not provided, the internal
+ 8 kHz clock is used.
+ maxItems: 1
+
+ reset-gpios:
+ description:
+ Optional GPIO for hardware reset, active low. Routes to the
+ device's GPIO0 pin, which can be independently set to the reset
+ function or left idle via an on-chip register.
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - spi-cpha
+ - spi-cpol
+ - vdd-supply
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/gpio/gpio.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ imu@0 {
+ compatible = "adi,adis16607-2";
+ reg = <0>;
+ spi-cpha;
+ spi-cpol;
+ spi-max-frequency = <10000000>;
+ vdd-supply = <&vdd>;
+ interrupts = <4 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&gpio>;
+ clocks = <&sync_clk>;
+ reset-gpios = <&gpio 10 GPIO_ACTIVE_LOW>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index f53047aa8d3c..d5679e8d0ffd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1792,6 +1792,13 @@ S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/imu/adi,adis16550.yaml
+ANALOG DEVICES INC ADIS16607 DRIVER
+M: Radu Sabau <radu.sabau@analog.com>
+L: linux-iio@vger.kernel.org
+S: Supported
+W: https://ez.analog.com/linux-software-drivers
+F: Documentation/devicetree/bindings/iio/imu/adi,adis16607.yaml
+
ANALOG DEVICES INC ADL8113 DRIVER
M: Antoniu Miclaus <antoniu.miclaus@analog.com>
L: linux-iio@vger.kernel.org
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v6 5/8] iio: imu: Add driver for the ADI ADIS16607
2026-11-13 12:33 [PATCH v6 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
` (3 preceding siblings ...)
2026-11-13 12:33 ` [PATCH v6 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607 Radu Sabau via B4 Relay
@ 2026-11-13 12:33 ` Radu Sabau via B4 Relay
2026-11-13 12:33 ` [PATCH v6 6/8] iio: imu: adis16607: Add FIFO-based buffered capture Radu Sabau via B4 Relay
` (3 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-13 12:33 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, Antoniu Miclaus,
Robert Budai, Ramona Gradinariu
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 | 12 +
drivers/iio/imu/Makefile | 1 +
drivers/iio/imu/adis16607.c | 869 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 883 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..8b91426f60d0 100644
--- a/drivers/iio/imu/Kconfig
+++ b/drivers/iio/imu/Kconfig
@@ -65,6 +65,18 @@ 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
+ 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..f5dc37c54191
--- /dev/null
+++ b/drivers/iio/imu/adis16607.c
@@ -0,0 +1,869 @@
+// 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/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;
+ u16 dec;
+};
+
+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)
+{
+ adis_dev_auto_lock(&st->adis);
+
+ *freq = DIV_ROUND_CLOSEST(st->clk_freq_mHz, st->dec + 1);
+
+ return 0;
+}
+
+static int adis16607_set_freq(struct adis16607 *st, u32 freq)
+{
+ 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);
+
+ 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;
+
+ st->dec = 0;
+
+ if (dec) {
+ ret = __adis_write_reg_16(&st->adis,
+ ADIS16607_REG_DEC_RATE, dec);
+ if (ret)
+ return ret;
+
+ st->dec = dec;
+ }
+ }
+
+ 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 */
+static int adis16607_config_clk(struct adis16607 *st)
+{
+ st->clk_freq_mHz = st->info->int_clk_Hz * MILLIHZ_PER_HZ;
+
+ return adis_read_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, &st->dec);
+}
+
+static int adis16607_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct adis16607 *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->info = spi_get_device_match_data(spi);
+ if (!st->info)
+ return -ENODATA;
+
+ indio_dev->name = st->info->name;
+ indio_dev->channels = st->info->channels;
+ indio_dev->num_channels = st->info->num_channels;
+ indio_dev->info = &adis16607_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ st->adis.ops = &adis16607_ops;
+
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get vdd regulator\n");
+
+ ret = adis_init(&st->adis, indio_dev, spi, &adis16607_data);
+ if (ret)
+ return ret;
+
+ /*
+ * Force GPIO0 back to its RESET function before relying on toggling
+ * it below - RESET_GPIO_MASK is runtime-writable on this part, so a
+ * prior write (buggy driver, direct reg access, etc.) could have
+ * left it de-routed, which would make the GPIO toggle in
+ * __adis_initial_startup() silently do nothing. This write goes out
+ * over SPI, which stays live regardless of this mask's state, so it
+ * can re-arm the mux as long as the device is merely
+ * reset-pin-defeated and not fully unresponsive. Only warn on
+ * failure instead of erroring out - bailing here would throw away
+ * whatever chance the GPIO toggle below still has of working.
+ */
+ if (device_property_present(dev, "reset-gpios")) {
+ ret = __adis_update_bits_base(&st->adis, ADIS16607_REG_USER_GPIO_CFG,
+ ADIS16607_RESET_GPIO_MASK,
+ FIELD_PREP(ADIS16607_RESET_GPIO_MASK, 1), 2);
+ if (ret)
+ dev_warn(dev, "failed to force reset-pin routing: %d\n", ret);
+ }
+
+ ret = __adis_initial_startup(&st->adis);
+ if (ret)
+ return ret;
+
+ ret = adis16607_config_clk(st);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret)
+ return ret;
+
+ adis16607_debugfs_init(indio_dev);
+
+ return 0;
+}
+
+static const struct spi_device_id adis16607_id[] = {
+ { .name = "adis16607-2", .driver_data = (kernel_ulong_t)&adis16607_2_chip_info },
+ { .name = "adis16607-3", .driver_data = (kernel_ulong_t)&adis16607_3_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, adis16607_id);
+
+static const struct of_device_id adis16607_of_match[] = {
+ { .compatible = "adi,adis16607-2", .data = &adis16607_2_chip_info },
+ { .compatible = "adi,adis16607-3", .data = &adis16607_3_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adis16607_of_match);
+
+static struct spi_driver adis16607_driver = {
+ .driver = {
+ .name = "adis16607",
+ .of_match_table = adis16607_of_match,
+ },
+ .probe = adis16607_probe,
+ .id_table = adis16607_id,
+};
+module_spi_driver(adis16607_driver);
+
+MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADIS16607 and similar IMU driver");
+MODULE_IMPORT_NS("IIO_ADISLIB");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v6 6/8] iio: imu: adis16607: Add FIFO-based buffered capture
2026-11-13 12:33 [PATCH v6 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
` (4 preceding siblings ...)
2026-11-13 12:33 ` [PATCH v6 5/8] iio: imu: Add driver for the " Radu Sabau via B4 Relay
@ 2026-11-13 12:33 ` Radu Sabau via B4 Relay
2026-11-13 12:51 ` sashiko-bot
2026-11-13 12:33 ` [PATCH v6 7/8] iio: imu: adis16607: Add external clock sync support Radu Sabau via B4 Relay
` (2 subsequent siblings)
8 siblings, 1 reply; 11+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-13 12:33 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, Antoniu Miclaus,
Robert Budai, Ramona Gradinariu
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).
The samples go to a kfifo buffer that a threaded interrupt handler
fills, with no IIO trigger: the device raises one interrupt per
watermark, not per sample, so there is nothing to trigger on. The
watermark-met condition is signaled via DEV_NOK on the device's one
interrupt pin, which stays asserted until DIAG_STAT is read and
cleared. The interrupt is therefore level-triggered and oneshot, so
the line stays masked until the handler has read DIAG_STAT, and it is
enabled only while the buffer is. DEV_NOK fires on any DIAG_STAT flag,
not just FIFO_THRESH_MET, so the 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. Changing the sampling
frequency while the buffer is enabled returns -EBUSY, as the decimation
can't change during capture.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/imu/Kconfig | 2 +
drivers/iio/imu/adis16607.c | 416 +++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 415 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig
index 8b91426f60d0..af6910e2c862 100644
--- a/drivers/iio/imu/Kconfig
+++ b/drivers/iio/imu/Kconfig
@@ -69,6 +69,8 @@ config ADIS16607
tristate "Analog Devices ADIS16607 and similar IMU driver"
depends on SPI
select IIO_ADIS_LIB
+ select IIO_BUFFER
+ select IIO_KFIFO_BUF
help
Say yes here to build support for Analog Devices ADIS16607-2
and ADIS16607-3 inertial sensors containing triaxis
diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
index f5dc37c54191..377e7c46f860 100644
--- a/drivers/iio/imu/adis16607.c
+++ b/drivers/iio/imu/adis16607.c
@@ -10,8 +10,12 @@
#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/iio/kfifo_buf.h>
+#include <linux/iio/sysfs.h>
+#include <linux/interrupt.h>
#include <linux/math.h>
#include <linux/minmax.h>
#include <linux/module.h>
@@ -42,8 +46,11 @@
#define ADIS16607_REG_Y_DELTANG 0x1A
#define ADIS16607_REG_Z_DELTANG 0x1C
#define ADIS16607_REG_TEMP 0x20
+#define ADIS16607_REG_FIFO_WORD_CNT 0x2B
#define ADIS16607_REG_USER_GPIO_CFG 0x2F
#define ADIS16607_REG_SPI_HALFDUPLEX 0x32
+#define ADIS16607_REG_USER_DATA_CFG 0x34
+#define ADIS16607_REG_USER_FIFO_CFG 0x35
#define ADIS16607_REG_SOFT_RESET 0x36
#define ADIS16607_REG_SELF_TEST 0x39
#define ADIS16607_REG_DEC_RATE 0x3A
@@ -54,8 +61,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)
@@ -86,6 +114,12 @@ struct adis16607 {
const struct adis16607_chip_info *info;
unsigned long clk_freq_mHz;
u16 dec;
+ 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 {
@@ -228,6 +262,333 @@ 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);
+}
+
+/* Called with the adis lock held, so dec and clk_freq_mHz match each other */
+static u64 adis16607_sample_period_ns(struct adis16607 *st)
+{
+ u64 period_ns;
+
+ /* clk_freq_mHz is in millihertz, hence the MILLIHZ_PER_HZ factor */
+ period_ns = NSEC_PER_SEC;
+ period_ns *= MILLIHZ_PER_HZ;
+ period_ns *= st->dec + 1;
+
+ return DIV_ROUND_CLOSEST_ULL(period_ns, st->clk_freq_mHz);
+}
+
+/*
+ * 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;
+ u64 period_ns;
+ s64 timestamp;
+ int ret;
+
+ adis_dev_auto_lock(&st->adis);
+
+ ret = __adis_read_reg_16(&st->adis, ADIS16607_REG_DIAG_STAT, &diag_stat);
+ if (ret)
+ return;
+
+ /* Check if FIFO threshold is met */
+ 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.
+ */
+ period_ns = adis16607_sample_period_ns(st);
+ timestamp = irq_timestamp - (s64)(sample_cnt - 1) * 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 += period_ns;
+ }
+}
+
+static irqreturn_t adis16607_irq_thread(int irq, void *p)
+{
+ struct iio_dev *indio_dev = p;
+
+ adis16607_fifo_drain(indio_dev, iio_get_time_ns(indio_dev));
+
+ 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;
+ ret = __adis_write_reg_16(adis, ADIS16607_REG_USER_FIFO_CFG,
+ threshold & ADIS16607_FIFO_THR_MASK);
+ if (ret)
+ return ret;
+
+ enable_irq(adis->spi->irq);
+
+ return 0;
+}
+
+static int adis16607_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct adis16607 *st = iio_priv(indio_dev);
+
+ /* Waits for a running handler, so no drain races the FIFO disable */
+ disable_irq(st->adis.spi->irq);
+
+ return 0;
+}
+
+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, so the next one
+ * does not start 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,
+ .predisable = adis16607_buffer_predisable,
+ .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.
*/
@@ -315,7 +676,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)
{
@@ -356,7 +718,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);
}
/*
@@ -593,7 +977,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;
@@ -601,6 +990,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;
@@ -700,6 +1090,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[] = {
@@ -792,6 +1183,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");
@@ -830,6 +1223,23 @@ static int adis16607_probe(struct spi_device *spi)
if (ret)
return ret;
+ ret = devm_iio_kfifo_buffer_setup_ext(dev, indio_dev, &adis16607_buffer_ops,
+ adis16607_fifo_attributes);
+ if (ret)
+ return ret;
+
+ /*
+ * DEV_NOK is the OR of the DIAG_STAT flags and stays high until
+ * DIAG_STAT is read. Use a level interrupt so a flag raised while it
+ * is already high is not lost; ONESHOT keeps it masked until the
+ * handler has read DIAG_STAT. It is enabled along with the buffer.
+ */
+ ret = devm_request_threaded_irq(dev, spi->irq, NULL, adis16607_irq_thread,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ indio_dev->name, indio_dev);
+ if (ret)
+ return ret;
+
ret = devm_iio_device_register(dev, indio_dev);
if (ret)
return ret;
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH v6 6/8] iio: imu: adis16607: Add FIFO-based buffered capture
2026-11-13 12:33 ` [PATCH v6 6/8] iio: imu: adis16607: Add FIFO-based buffered capture Radu Sabau via B4 Relay
@ 2026-11-13 12:51 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-11-13 12:51 UTC (permalink / raw)
To: Radu Sabau; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Low] iio: imu: adis16607: initial fifo_watermark in adis16607_probe()
is overwritten on buffer enable
--
commit 432b63f4ff217d947afef093d1237f760013e881
Author: Radu Sabau <radu.sabau@analog.com>
iio: imu: adis16607: Add FIFO-based buffered capture
This commit adds hardware FIFO buffered capture support to the adis16607
driver using a kfifo buffer, a level-triggered threaded interrupt handler,
and configurable watermark attributes.
> diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
> index f5dc37c54191e..377e7c46f860b 100644
> --- a/drivers/iio/imu/adis16607.c
> +++ b/drivers/iio/imu/adis16607.c
[ ... ]
> @@ -228,6 +262,333 @@ static const struct adis_ops adis16607_ops = {
[ ... ]
> +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 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;
> +}
[ ... ]
> @@ -792,6 +1183,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;
[Severity: Low]
Does initializing st->fifo_watermark to ADIS16607_MAX_FIFO_WM take effect
when the buffer is enabled?
devm_iio_kfifo_buffer_setup_ext() initializes buffer->watermark to 1 via
iio_kfifo_allocate() -> iio_buffer_init(), and iio_enable_buffers() calls
hwfifo_set_watermark() with that value before postenable() runs. As a
result, hwfifo_watermark reports 38 in sysfs before the first capture while
buffer/watermark is 1, and enabling the buffer immediately overwrites
st->fifo_watermark with 1 before adis16607_buffer_postenable() programs
ADIS16607_REG_USER_FIFO_CFG.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-adis16607-v6-0-3f678719942a@analog.com?part=6
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v6 7/8] iio: imu: adis16607: Add external clock sync support
2026-11-13 12:33 [PATCH v6 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
` (5 preceding siblings ...)
2026-11-13 12:33 ` [PATCH v6 6/8] iio: imu: adis16607: Add FIFO-based buffered capture Radu Sabau via B4 Relay
@ 2026-11-13 12:33 ` Radu Sabau via B4 Relay
2026-11-13 12:33 ` [PATCH v6 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel Radu Sabau via B4 Relay
2026-11-13 12:41 ` [PATCH v6 0/8] iio: imu: Add support for the ADI ADIS16607 Sabau, Radu bogdan
8 siblings, 0 replies; 11+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-13 12:33 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, Antoniu Miclaus,
Robert Budai, Ramona Gradinariu
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 interrupt handler, as the device keeps sampling
without it.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/imu/adis16607.c | 198 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 192 insertions(+), 6 deletions(-)
diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
index 377e7c46f860..cd5f875d4c56 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>
@@ -49,6 +50,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
@@ -61,12 +63,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)
@@ -94,8 +102,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;
@@ -107,12 +129,16 @@ struct adis16607_chip_info {
u32 deltvel_max_val;
u32 int_clk_Hz;
u16 max_dec;
+ u8 num_sync;
};
struct adis16607 {
struct adis adis;
const struct adis16607_chip_info *info;
unsigned long clk_freq_mHz;
+ unsigned long ext_clk_Hz;
+ u32 sync_mode;
+ u16 sync_scale;
u16 dec;
u16 fifo_watermark;
/* IIO buffer for reordered data - sized for max channels */
@@ -424,6 +450,13 @@ static void adis16607_fifo_drain(struct iio_dev *indio_dev, s64 irq_timestamp)
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;
@@ -864,19 +897,60 @@ 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;
u32 dec;
int ret;
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);
+ }
adis_dev_auto_scoped_lock(&st->adis) {
/* Reset the decimation accumulator before the new value */
@@ -886,6 +960,21 @@ static int adis16607_set_freq(struct adis16607 *st, u32 freq)
st->dec = 0;
+ /*
+ * 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);
@@ -1093,6 +1182,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",
@@ -1136,6 +1236,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 = {
@@ -1151,12 +1253,96 @@ static const struct adis16607_chip_info adis16607_3_chip_info = {
.deltvel_max_val = IIO_G_TO_M_S_2(40),
.int_clk_Hz = 8000,
.max_dec = 65535,
+ .sync_mode = adis16607_sync_modes,
+ .num_sync = ARRAY_SIZE(adis16607_sync_modes),
};
-/* No external clock sync support yet: always run on the internal clock */
static int adis16607_config_clk(struct adis16607 *st)
{
- st->clk_freq_mHz = st->info->int_clk_Hz * MILLIHZ_PER_HZ;
+ const struct adis16607_sync *sync_mode_data = NULL;
+ struct device *dev = &st->adis.spi->dev;
+ struct clk *clk;
+ 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;
+
+ 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;
+
+ 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 the 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;
+ }
return adis_read_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, &st->dec);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v6 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel
2026-11-13 12:33 [PATCH v6 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
` (6 preceding siblings ...)
2026-11-13 12:33 ` [PATCH v6 7/8] iio: imu: adis16607: Add external clock sync support Radu Sabau via B4 Relay
@ 2026-11-13 12:33 ` Radu Sabau via B4 Relay
2026-11-13 12:41 ` [PATCH v6 0/8] iio: imu: Add support for the ADI ADIS16607 Sabau, Radu bogdan
8 siblings, 0 replies; 11+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-11-13 12:33 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, Antoniu Miclaus,
Robert Budai, Ramona Gradinariu
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 | 40 +++++++++++++++++++++++++++++++++-------
1 file changed, 33 insertions(+), 7 deletions(-)
diff --git a/drivers/iio/imu/adis16607.c b/drivers/iio/imu/adis16607.c
index cd5f875d4c56..84fd5b9ad8a5 100644
--- a/drivers/iio/imu/adis16607.c
+++ b/drivers/iio/imu/adis16607.c
@@ -56,6 +56,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 */
@@ -167,15 +173,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 },
@@ -1043,6 +1050,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)
@@ -1081,6 +1098,14 @@ static int adis16607_write_raw(struct iio_dev *indio_dev,
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;
}
@@ -1095,7 +1120,8 @@ static int adis16607_write_raw(struct iio_dev *indio_dev,
.type = (_type), \
.modified = 1, \
.channel2 = (_mod), \
- .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
.address = (_idx), \
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* RE: [PATCH v6 0/8] iio: imu: Add support for the ADI ADIS16607
2026-11-13 12:33 [PATCH v6 0/8] iio: imu: Add support for the ADI ADIS16607 Radu Sabau via B4 Relay
` (7 preceding siblings ...)
2026-11-13 12:33 ` [PATCH v6 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel Radu Sabau via B4 Relay
@ 2026-11-13 12:41 ` Sabau, Radu bogdan
8 siblings, 0 replies; 11+ messages in thread
From: Sabau, Radu bogdan @ 2026-11-13 12:41 UTC (permalink / raw)
To: Sabau, Radu bogdan, Lars-Peter Clausen, Hennerich, Michael,
Sa, Nuno, Jonathan Cameron, David Lechner, Andy Shevchenko,
Alexandru Ardelean, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Miclaus, Antoniu, Budai, Robert, Gradinariu, Ramona
Cc: linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org,
devicetree@vger.kernel.org, linux, Conor Dooley
> -----Original Message-----
> From: Radu Sabau via B4 Relay <devnull+radu.sabau.analog.com@kernel.org>
> Sent: Friday, November 13, 2026 2:34 PM
...
> 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, into a
> kfifo buffer filled from a threaded interrupt (no IIO trigger).
Note: this series implements the FIFO buffered capture with a kfifo
buffer for the adis16607 only, using its own threaded interrupt instead
of adislib's has_fifo trigger path. I tested it on hardware and it
behaves better than the trigger-based version in v5: the interrupt now
fires once per watermark batch instead of roughly once per sample, and
capture stays clean across repeated buffer enable/disable cycles.
I plan a follow-up series that updates adislib and the other affected
devices (the adis16475 FIFO variants) to use a kfifo buffer as well, and
then converts this driver to use the adislib helpers.
If you would rather have it handled here, I can instead do the adislib
and adis16475 changes in this series, as patches ahead of the driver.
^ permalink raw reply [flat|nested] 11+ messages in thread