devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/8] iio: imu: new inv_icm45600 driver
@ 2025-04-11 13:28 Remi Buisson via B4 Relay
  2025-04-11 13:28 ` [PATCH 1/8] iio: imu: inv_icm45600: add " Remi Buisson via B4 Relay
                   ` (7 more replies)
  0 siblings, 8 replies; 19+ messages in thread
From: Remi Buisson via B4 Relay @ 2025-04-11 13:28 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-kernel, linux-iio, devicetree, Remi Buisson

This series add a new driver for managing InvenSense ICM-456xx 6-axis IMUs.
This next generation of chips includes new generations of 3-axis gyroscope
and 3-axis accelerometer, support of I3C in addition to I2C and SPI, and
intelligent MotionTracking features like pedometer, tilt detection, and
tap detection.

This series is delivering a driver supporting gyroscope, accelerometer and
temperature data, with polling and buffering using hwfifo and watermark,
on I2C, SPI and I3C busses.

Gyroscope and accelerometer sensors are completely independent and can have
different ODRs. Since there is only a single FIFO a specific value is used to
mark invalid data. For keeping the device standard we are de-multiplexing data
from the FIFO to 2 IIO devices with 2 buffers, 1 for the accelerometer and 1
for the gyroscope. This architecture also enables to easily turn each sensor
on/off without impacting the other. The device interrupt is used to read the
FIFO and launch parsing of accelerometer and gyroscope data. This driver
relies on the common Invensense timestamping mechanism to handle correctly
FIFO watermark and dynamic changes of settings.

The structure of the driver is quite similar to the inv_icm42600 driver,
however there are significant reasons for adding a different driver for
inv_icm45600, such as:
- A completely different register map.
- Different FIFO management, based on number of samples instead of bytes.
- Different indirect register access mechanism.

Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
---
Remi Buisson (8):
      iio: imu: inv_icm45600: add new inv_icm45600 driver
      iio: imu: inv_icm45600: add I2C driver for inv_icm45600 driver
      iio: imu: inv_icm45600: add SPI driver for inv_icm45600 driver
      iio: imu: inv_icm45600: add I3C driver for inv_icm45600 driver
      iio: imu: inv_icm45600: add buffer support in iio devices
      iio: imu: add Kconfig and Makefile for inv_icm45600 driver
      dt-bindings: iio: imu: Add inv_icm45600 documentation
      MAINTAINERS: add entry for inv_icm45600 6-axis imu sensor

 .../ABI/testing/sysfs-bus-iio-inv_icm45600         |  37 +
 .../bindings/iio/imu/invensense,icm45600.yaml      | 136 +++
 MAINTAINERS                                        |   9 +
 drivers/iio/imu/Kconfig                            |   1 +
 drivers/iio/imu/Makefile                           |   1 +
 drivers/iio/imu/inv_icm45600/Kconfig               |  70 ++
 drivers/iio/imu/inv_icm45600/Makefile              |  17 +
 drivers/iio/imu/inv_icm45600/inv_icm45600.h        | 421 ++++++++++
 drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c  | 902 ++++++++++++++++++++
 drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c | 572 +++++++++++++
 drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h | 100 +++
 drivers/iio/imu/inv_icm45600/inv_icm45600_core.c   | 906 ++++++++++++++++++++
 drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c   | 919 +++++++++++++++++++++
 drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c    | 103 +++
 drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c    |  84 ++
 drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c    | 110 +++
 drivers/iio/imu/inv_icm45600/inv_icm45600_temp.c   |  82 ++
 drivers/iio/imu/inv_icm45600/inv_icm45600_temp.h   |  31 +
 18 files changed, 4501 insertions(+)
---
base-commit: 1c2409fe38d5c19015d69851d15ba543d1911932
change-id: 20250411-add_newport_driver-529cf5b71ea8

Best regards,
-- 
Remi Buisson <remi.buisson@tdk.com>



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

* [PATCH 1/8] iio: imu: inv_icm45600: add new inv_icm45600 driver
  2025-04-11 13:28 [PATCH 0/8] iio: imu: new inv_icm45600 driver Remi Buisson via B4 Relay
@ 2025-04-11 13:28 ` Remi Buisson via B4 Relay
  2025-04-12 19:10   ` Jonathan Cameron
  2025-04-11 13:28 ` [PATCH 2/8] iio: imu: inv_icm45600: add I2C driver for " Remi Buisson via B4 Relay
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 19+ messages in thread
From: Remi Buisson via B4 Relay @ 2025-04-11 13:28 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-kernel, linux-iio, devicetree, Remi Buisson

From: Remi Buisson <remi.buisson@tdk.com>

Core component of a new driver for InvenSense ICM-456xx devices.
It includes:
- registers definition, main probe/setup, and device
utility functions.
- IIO device for gyroscope sensor with data polling interface.
Attributes: raw, scale, sampling_frequency, calibbias.
- IIO device for gyroscope sensor with data polling interface.
Attributes: raw, scale, sampling_frequency, calibbias.
- Temperature is available as a processed
channel

ICM-456xx devices are latest generation of 6-axis IMU,
gyroscope+accelerometer and temperature sensor. This device
includes a 8K FIFO, supports I2C/I3C/SPI, and provides
intelligent motion features like pedometer, tilt detection,
and tap detection.

Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
---
 drivers/iio/imu/inv_icm45600/inv_icm45600.h       | 421 ++++++++++
 drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c | 902 +++++++++++++++++++++
 drivers/iio/imu/inv_icm45600/inv_icm45600_core.c  | 906 +++++++++++++++++++++
 drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c  | 919 ++++++++++++++++++++++
 drivers/iio/imu/inv_icm45600/inv_icm45600_temp.c  |  82 ++
 drivers/iio/imu/inv_icm45600/inv_icm45600_temp.h  |  31 +
 6 files changed, 3261 insertions(+)

diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600.h b/drivers/iio/imu/inv_icm45600/inv_icm45600.h
new file mode 100644
index 0000000000000000000000000000000000000000..6d10b3ffabbcbc054986d6cc0011891863b74e1a
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600.h
@@ -0,0 +1,421 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2025 Invensense, Inc.
+ */
+
+#ifndef INV_ICM45600_H_
+#define INV_ICM45600_H_
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/iio/common/inv_sensors_timestamp.h>
+#include <linux/iio/iio.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include "inv_icm45600_buffer.h"
+
+#define INV_ICM45600_REG_GET_BANK(_r)	FIELD_GET(GENMASK(15, 8), (_r))
+#define INV_ICM45600_REG_GET_ADDR(_r)	FIELD_GET(GENMASK(7, 0), (_r))
+
+enum inv_icm45600_chip {
+	INV_CHIP_INVALID,
+	INV_CHIP_ICM45605,
+	INV_CHIP_ICM45686,
+	INV_CHIP_ICM45688P,
+	INV_CHIP_ICM45608,
+	INV_CHIP_ICM45634,
+	INV_CHIP_ICM45689,
+	INV_CHIP_ICM45606,
+	INV_CHIP_ICM45687,
+	INV_CHIP_NB,
+};
+
+enum inv_icm45600_sensor_mode {
+	INV_ICM45600_SENSOR_MODE_OFF,
+	INV_ICM45600_SENSOR_MODE_STANDBY,
+	INV_ICM45600_SENSOR_MODE_LOW_POWER,
+	INV_ICM45600_SENSOR_MODE_LOW_NOISE,
+	INV_ICM45600_SENSOR_MODE_NB,
+};
+
+/* gyroscope fullscale values */
+enum inv_icm45600_gyro_fs {
+	INV_ICM45600_GYRO_FS_2000DPS,
+	INV_ICM45600_GYRO_FS_1000DPS,
+	INV_ICM45600_GYRO_FS_500DPS,
+	INV_ICM45600_GYRO_FS_250DPS,
+	INV_ICM45600_GYRO_FS_125DPS,
+	INV_ICM45600_GYRO_FS_62_5DPS,
+	INV_ICM45600_GYRO_FS_31_25DPS,
+	INV_ICM45600_GYRO_FS_15_625DPS,
+	INV_ICM45600_GYRO_FS_NB,
+};
+enum inv_icm45686_gyro_fs {
+	INV_ICM45686_GYRO_FS_4000DPS,
+	INV_ICM45686_GYRO_FS_2000DPS,
+	INV_ICM45686_GYRO_FS_1000DPS,
+	INV_ICM45686_GYRO_FS_500DPS,
+	INV_ICM45686_GYRO_FS_250DPS,
+	INV_ICM45686_GYRO_FS_125DPS,
+	INV_ICM45686_GYRO_FS_62_5DPS,
+	INV_ICM45686_GYRO_FS_31_25DPS,
+	INV_ICM45686_GYRO_FS_15_625DPS,
+	INV_ICM45686_GYRO_FS_NB,
+};
+
+/* accelerometer fullscale values */
+enum inv_icm45600_accel_fs {
+	INV_ICM45600_ACCEL_FS_16G,
+	INV_ICM45600_ACCEL_FS_8G,
+	INV_ICM45600_ACCEL_FS_4G,
+	INV_ICM45600_ACCEL_FS_2G,
+	INV_ICM45600_ACCEL_FS_NB,
+};
+enum inv_icm45686_accel_fs {
+	INV_ICM45686_ACCEL_FS_32G,
+	INV_ICM45686_ACCEL_FS_16G,
+	INV_ICM45686_ACCEL_FS_8G,
+	INV_ICM45686_ACCEL_FS_4G,
+	INV_ICM45686_ACCEL_FS_2G,
+	INV_ICM45686_ACCEL_FS_NB,
+};
+
+/* ODR suffixed by LN or LP are Low-Noise or Low-Power mode only */
+enum inv_icm45600_odr {
+	INV_ICM45600_ODR_6400HZ_LN = 0x03,
+	INV_ICM45600_ODR_3200HZ_LN,
+	INV_ICM45600_ODR_1600HZ_LN,
+	INV_ICM45600_ODR_800HZ_LN,
+	INV_ICM45600_ODR_400HZ,
+	INV_ICM45600_ODR_200HZ,
+	INV_ICM45600_ODR_100HZ,
+	INV_ICM45600_ODR_50HZ,
+	INV_ICM45600_ODR_25HZ,
+	INV_ICM45600_ODR_12_5HZ,
+	INV_ICM45600_ODR_6_25HZ_LP,
+	INV_ICM45600_ODR_3_125HZ_LP,
+	INV_ICM45600_ODR_1_5625HZ_LP,
+	INV_ICM45600_ODR_NB,
+};
+
+struct inv_icm45600_sensor_conf {
+	int mode;
+	int fs;
+	int odr;
+	int filter;
+};
+#define INV_ICM45600_SENSOR_CONF_INIT		{-1, -1, -1, -1}
+
+struct inv_icm45600_conf {
+	struct inv_icm45600_sensor_conf gyro;
+	struct inv_icm45600_sensor_conf accel;
+};
+
+struct inv_icm45600_suspended {
+	enum inv_icm45600_sensor_mode gyro;
+	enum inv_icm45600_sensor_mode accel;
+};
+
+/**
+ *  struct inv_icm45600_state - driver state variables
+ *  @lock:		lock for serializing multiple registers access.
+ *  @chip:		chip identifier.
+ *  @name:		chip name.
+ *  @map:		regmap pointer.
+ *  @vdd_supply:	VDD voltage regulator for the chip.
+ *  @vddio_supply:	I/O voltage regulator for the chip.
+ *  @orientation:	sensor chip orientation relative to main hardware.
+ *  @conf:		chip sensors configurations.
+ *  @suspended:		suspended sensors configuration.
+ *  @indio_gyro:	gyroscope IIO device.
+ *  @indio_accel:	accelerometer IIO device.
+ *  @timestamp:		interrupt timestamps.
+ *  @fifo:		FIFO management structure.
+ *  @buffer:		data transfer buffer aligned for DMA.
+ */
+struct inv_icm45600_state {
+	struct mutex lock;
+	enum inv_icm45600_chip chip;
+	const char *name;
+	struct regmap *map;
+	struct regulator *vdd_supply;
+	struct regulator *vddio_supply;
+	struct iio_mount_matrix orientation;
+	struct inv_icm45600_conf conf;
+	struct inv_icm45600_suspended suspended;
+	struct iio_dev *indio_gyro;
+	struct iio_dev *indio_accel;
+	struct {
+		int64_t gyro;
+		int64_t accel;
+	} timestamp;
+	struct inv_icm45600_fifo fifo;
+	uint8_t buffer[2] __aligned(IIO_DMA_MINALIGN);
+};
+
+
+/**
+ * struct inv_icm45600_sensor_state - sensor state variables
+ * @scales:		table of scales.
+ * @scales_len:		length (nb of items) of the scales table.
+ * @power_mode:		sensor requested power mode (for common frequencies)
+ * @ts:			timestamp module states.
+ */
+struct inv_icm45600_sensor_state {
+	const int *scales;
+	size_t scales_len;
+	enum inv_icm45600_sensor_mode power_mode;
+	struct inv_sensors_timestamp ts;
+};
+
+/* Virtual register addresses: @bank on MSB (16 bits), @address on LSB */
+
+/* Indirect register access */
+#define INV_ICM45600_REG_IREG_ADDR			0x7C
+#define INV_ICM45600_REG_IREG_DATA			0x7E
+
+/* Direct acces registers */
+#define INV_ICM45600_REG_MISC2				0x007F
+#define INV_ICM45600_MISC2_SOFT_RESET			BIT(1)
+
+#define INV_ICM45600_REG_DRIVE_CONFIG0			0x0032
+#define INV_ICM45600_DRIVE_CONFIG0_I2C_MASK		GENMASK(6, 4)
+#define INV_ICM45600_DRIVE_CONFIG0_I2C(_rate)		\
+		FIELD_PREP(INV_ICM45600_DRIVE_CONFIG0_I2C_MASK, (_rate))
+#define INV_ICM45600_I2C_SLEW_RATE_7NS				\
+		INV_ICM45600_DRIVE_CONFIG0_I2C(2)
+#define INV_ICM45600_I2C_SLEW_RATE_20NS				\
+		INV_ICM45600_DRIVE_CONFIG0_I2C(0)
+#define INV_ICM45600_DRIVE_CONFIG0_SPI_MASK		GENMASK(3, 1)
+#define INV_ICM45600_DRIVE_CONFIG0_SPI(_rate)		\
+		FIELD_PREP(INV_ICM45600_DRIVE_CONFIG0_SPI_MASK, (_rate))
+#define INV_ICM45600_SPI_SLEW_RATE_0_5NS			\
+		INV_ICM45600_DRIVE_CONFIG0_SPI(6)
+#define INV_ICM45600_SPI_SLEW_RATE_4NS				\
+		INV_ICM45600_DRIVE_CONFIG0_SPI(5)
+#define INV_ICM45600_SPI_SLEW_RATE_5NS				\
+		INV_ICM45600_DRIVE_CONFIG0_SPI(4)
+#define INV_ICM45600_SPI_SLEW_RATE_7NS				\
+		INV_ICM45600_DRIVE_CONFIG0_SPI(3)
+#define INV_ICM45600_SPI_SLEW_RATE_10NS				\
+		INV_ICM45600_DRIVE_CONFIG0_SPI(2)
+#define INV_ICM45600_SPI_SLEW_RATE_14NS				\
+		INV_ICM45600_DRIVE_CONFIG0_SPI(1)
+#define INV_ICM45600_SPI_SLEW_RATE_38NS				\
+		INV_ICM45600_DRIVE_CONFIG0_SPI(0)
+
+#define INV_ICM45600_REG_DRIVE_CONFIG1			0x0033
+#define INV_ICM45600_DRIVE_CONFIG1_I3C_DDR_SLEW_MASK	GENMASK(5, 3)
+#define INV_ICM45600_DRIVE_CONFIG1_I3C_DDR_SLEW(_rate)		\
+		FIELD_PREP(INV_ICM45600_DRIVE_CONFIG1_I3C_DDR_SLEW_MASK, (_rate))
+#define INV_ICM45600_I3C_DDR_SLEW_0_5NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_DDR_SLEW(6)
+#define INV_ICM45600_I3C_DDR_SLEW_4NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_DDR_SLEW(5)
+#define INV_ICM45600_I3C_DDR_SLEW_5NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_DDR_SLEW(4)
+#define INV_ICM45600_I3C_DDR_SLEW_7NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_DDR_SLEW(3)
+#define INV_ICM45600_I3C_DDR_SLEW_10NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_DDR_SLEW(2)
+#define INV_ICM45600_I3C_DDR_SLEW_14NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_DDR_SLEW(1)
+#define INV_ICM45600_I3C_DDR_SLEW_38NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_DDR_SLEW(0)
+#define INV_ICM45600_DRIVE_CONFIG1_I3C_SDR_SLEW_MASK	GENMASK(2, 0)
+#define INV_ICM45600_DRIVE_CONFIG1_I3C_SDR_SLEW(_rate)		\
+		FIELD_PREP(INV_ICM45600_DRIVE_CONFIG1_I3C_SDR_SLEW_MASK, (_rate))
+#define INV_ICM45600_I3C_SDR_SLEW_0_5NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_SDR_SLEW(6)
+#define INV_ICM45600_I3C_SDR_SLEW_4NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_SDR_SLEW(5)
+#define INV_ICM45600_I3C_SDR_SLEW_5NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_SDR_SLEW(4)
+#define INV_ICM45600_I3C_SDR_SLEW_7NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_SDR_SLEW(3)
+#define INV_ICM45600_I3C_SDR_SLEW_10NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_SDR_SLEW(2)
+#define INV_ICM45600_I3C_SDR_SLEW_14NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_SDR_SLEW(1)
+#define INV_ICM45600_I3C_SDR_SLEW_38NS				\
+		INV_ICM45600_DRIVE_CONFIG1_I3C_SDR_SLEW(0)
+
+#define INV_ICM45600_REG_INT1_CONFIG2			0x0018
+#define INV_ICM45600_INT1_CONFIG2_PUSH_PULL		BIT(2)
+#define INV_ICM45600_INT1_CONFIG2_LATCHED		BIT(1)
+#define INV_ICM45600_INT1_CONFIG2_ACTIVE_HIGH		BIT(0)
+#define INV_ICM45600_INT1_CONFIG2_ACTIVE_LOW		0x00
+
+#define INV_ICM45600_REG_FIFO_CONFIG0			0x001D
+#define INV_ICM45600_FIFO_CONFIG0_MODE_MASK		GENMASK(7, 6)
+#define INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS			\
+		FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK, 0)
+#define INV_ICM45600_FIFO_CONFIG0_MODE_STREAM			\
+		FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK, 1)
+#define INV_ICM45600_FIFO_CONFIG0_MODE_STOP_ON_FULL		\
+		FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK, 2)
+#define INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MAX	0x1F
+
+#define INV_ICM45600_REG_FIFO_CONFIG2			0x0020
+#define INV_ICM45600_REG_FIFO_CONFIG2_FIFO_FLUSH	BIT(7)
+#define INV_ICM45600_REG_FIFO_CONFIG2_WM_GT_TH		BIT(3)
+
+#define INV_ICM45600_REG_FIFO_CONFIG3			0x0021
+#define INV_ICM45600_FIFO_CONFIG3_ES1_EN		BIT(5)
+#define INV_ICM45600_FIFO_CONFIG3_ES0_EN		BIT(4)
+#define INV_ICM45600_FIFO_CONFIG3_HIRES_EN		BIT(3)
+#define INV_ICM45600_FIFO_CONFIG3_GYRO_EN		BIT(2)
+#define INV_ICM45600_FIFO_CONFIG3_ACCEL_EN		BIT(1)
+#define INV_ICM45600_FIFO_CONFIG3_IF_EN			BIT(0)
+
+#define INV_ICM45600_REG_FIFO_CONFIG4			0x0022
+#define INV_ICM45600_FIFO_CONFIG4_COMP_EN		BIT(2)
+#define INV_ICM45600_FIFO_CONFIG4_TMST_FSYNC_EN		BIT(1)
+#define INV_ICM45600_FIFO_CONFIG4_ES0_9B		BIT(0)
+
+/* all sensor data are 16 bits (2 registers wide) in big-endian */
+#define INV_ICM45600_REG_TEMP_DATA			0x000C
+#define INV_ICM45600_REG_ACCEL_DATA_X			0x0000
+#define INV_ICM45600_REG_ACCEL_DATA_Y			0x0002
+#define INV_ICM45600_REG_ACCEL_DATA_Z			0x0004
+#define INV_ICM45600_REG_GYRO_DATA_X			0x0006
+#define INV_ICM45600_REG_GYRO_DATA_Y			0x0008
+#define INV_ICM45600_REG_GYRO_DATA_Z			0x000A
+#define INV_ICM45600_DATA_INVALID			-32768
+
+#define INV_ICM45600_REG_INT_STATUS			0x0019
+#define INV_ICM45600_INT_STATUS_RESET_DONE		BIT(7)
+#define INV_ICM45600_INT_STATUS_AUX1_AGC_RDY		BIT(6)
+#define INV_ICM45600_INT_STATUS_AP_AGC_RDY		BIT(5)
+#define INV_ICM45600_INT_STATUS_AP_FSYNC		BIT(4)
+#define INV_ICM45600_INT_STATUS_AUX1_DRDY		BIT(3)
+#define INV_ICM45600_INT_STATUS_DATA_RDY		BIT(2)
+#define INV_ICM45600_INT_STATUS_FIFO_THS		BIT(1)
+#define INV_ICM45600_INT_STATUS_FIFO_FULL		BIT(0)
+
+/*
+ * FIFO access registers
+ * FIFO count is 16 bits (2 registers)
+ * FIFO data is a continuous read register to read FIFO content
+ */
+#define INV_ICM45600_REG_FIFO_COUNT			0x0012
+#define INV_ICM45600_REG_FIFO_DATA			0x0014
+
+#define INV_ICM45600_REG_PWR_MGMT0			0x0010
+#define INV_ICM45600_PWR_MGMT0_GYRO(_mode)		\
+		FIELD_PREP(GENMASK(3, 2), (_mode))
+#define INV_ICM45600_PWR_MGMT0_ACCEL(_mode)		\
+		FIELD_PREP(GENMASK(1, 0), (_mode))
+
+#define INV_ICM45600_REG_ACCEL_CONFIG0			0x001B
+#define INV_ICM45600_ACCEL_CONFIG0_FS_MASK		GENMASK(6, 4)
+#define INV_ICM45600_ACCEL_CONFIG0_FS(_fs)		\
+		FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_FS_MASK, (_fs))
+#define INV_ICM45600_ACCEL_CONFIG0_FS_16G		\
+		INV_ICM45600_ACCEL_CONFIG0_FS(1)
+#define INV_ICM45600_ACCEL_CONFIG0_ODR(_odr)		\
+		FIELD_PREP(GENMASK(3, 0), (_odr))
+#define INV_ICM45600_REG_GYRO_CONFIG0			0x001C
+#define INV_ICM45600_GYRO_CONFIG0_FS_MASK		GENMASK(7, 4)
+#define INV_ICM45600_GYRO_CONFIG0_FS(_fs)		\
+		FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_FS_MASK, (_fs))
+#define INV_ICM45600_GYRO_CONFIG0_FS_2000DPS		\
+		INV_ICM45600_GYRO_CONFIG0_FS(1)
+#define INV_ICM45600_GYRO_CONFIG0_ODR(_odr)		\
+		FIELD_PREP(GENMASK(3, 0), (_odr))
+
+#define INV_ICM45600_REG_SMC_CONTROL_0			0xA258
+#define INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL	BIT(4)
+#define INV_ICM45600_SMC_CONTROL_0_TMST_EN		BIT(0)
+
+/* FIFO watermark is 16 bits (2 registers wide) in little-endian */
+#define INV_ICM45600_REG_FIFO_WATERMARK			0x001E
+#define INV_ICM45600_FIFO_WATERMARK_VAL(_wm)		\
+		cpu_to_le16(_wm)
+/* FIFO is configured for 8kb */
+#define INV_ICM45600_FIFO_SIZE_MAX			(8 * 1024)
+
+#define INV_ICM45600_REG_INT1_CONFIG0			0x0016
+#define INV_ICM45600_INT1_CONFIG0_RESET_DONE_EN		BIT(7)
+#define INV_ICM45600_INT1_CONFIG0_AUX1_AGC_RDY_EN	BIT(6)
+#define INV_ICM45600_INT1_CONFIG0_AP_AGC_RDY_EN		BIT(5)
+#define INV_ICM45600_INT1_CONFIG0_AP_FSYNC_EN		BIT(4)
+#define INV_ICM45600_INT1_CONFIG0_AUX1_DRDY_EN		BIT(3)
+#define INV_ICM45600_INT1_CONFIG0_DRDY_EN		BIT(2)
+#define INV_ICM45600_INT1_CONFIG0_FIFO_THS_EN		BIT(1)
+#define INV_ICM45600_INT1_CONFIG0_FIFO_FULL_EN		BIT(0)
+
+#define INV_ICM45600_REG_WHOAMI				0x0072
+#define INV_ICM45600_WHOAMI_ICM45605			0xE5
+#define INV_ICM45600_WHOAMI_ICM45686			0xE9
+#define INV_ICM45600_WHOAMI_ICM45688P			0xE7
+#define INV_ICM45600_WHOAMI_ICM45608			0x81
+#define INV_ICM45600_WHOAMI_ICM45634			0x82
+#define INV_ICM45600_WHOAMI_ICM45689			0x83
+#define INV_ICM45600_WHOAMI_ICM45606			0x84
+#define INV_ICM45600_WHOAMI_ICM45687			0x85
+
+/* Gyro USER offset */
+#define INV_ICM45600_IPREG_SYS1_REG_42			0xA42A
+#define INV_ICM45600_IPREG_SYS1_REG_56			0xA438
+#define INV_ICM45600_IPREG_SYS1_REG_70			0xA446
+/* Gyro Averaging filter */
+#define INV_ICM45600_IPREG_SYS1_REG_170			0xA4AA
+#define INV_ICM45600_IPREG_SYS1_REG_170_MASK		GENMASK(4, 1)
+#define INV_ICM45600_GYRO_LP_AVG_SEL_8X			\
+	FIELD_PREP_CONST(INV_ICM45600_IPREG_SYS1_REG_170_MASK, 5)
+#define INV_ICM45600_GYRO_LP_AVG_SEL_2X			\
+	FIELD_PREP_CONST(INV_ICM45600_IPREG_SYS1_REG_170_MASK, 1)
+/* Accel USER offset */
+#define INV_ICM45600_IPREG_SYS2_REG_24			0xA518
+#define INV_ICM45600_IPREG_SYS2_REG_32			0xA520
+#define INV_ICM45600_IPREG_SYS2_REG_40			0xA528
+/* Accel averaging filter */
+#define INV_ICM45600_IPREG_SYS2_REG_129			0xA581
+#define INV_ICM45600_ACCEL_LP_AVG_SEL_4X		0x0002
+
+/* Sleep times required by the driver */
+#define INV_ICM45600_POWER_UP_TIME_MS		100
+#define INV_ICM45600_RESET_TIME_MS		1
+#define INV_ICM45600_ACCEL_STARTUP_TIME_MS	60
+#define INV_ICM45600_GYRO_STARTUP_TIME_MS	60
+#define INV_ICM45600_GYRO_STOP_TIME_MS		150
+#define INV_ICM45600_SUSPEND_DELAY_MS		2000
+#define INV_ICM45600_IREG_DELAY_US		4
+
+typedef int (*inv_icm45600_bus_setup)(struct inv_icm45600_state *);
+
+extern const struct dev_pm_ops inv_icm45600_pm_ops;
+
+const struct iio_mount_matrix *
+inv_icm45600_get_mount_matrix(const struct iio_dev *indio_dev,
+			      const struct iio_chan_spec *chan);
+
+uint32_t inv_icm45600_odr_to_period(enum inv_icm45600_odr odr);
+
+int inv_icm45600_set_accel_conf(struct inv_icm45600_state *st,
+				struct inv_icm45600_sensor_conf *conf,
+				unsigned int *sleep_ms);
+
+int inv_icm45600_set_gyro_conf(struct inv_icm45600_state *st,
+			       struct inv_icm45600_sensor_conf *conf,
+			       unsigned int *sleep_ms);
+
+int inv_icm45600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg,
+			     unsigned int writeval, unsigned int *readval);
+
+int inv_icm45600_core_probe(struct regmap *regmap, int chip,
+			    bool reset, inv_icm45600_bus_setup bus_setup);
+
+struct iio_dev *inv_icm45600_gyro_init(struct inv_icm45600_state *st);
+
+int inv_icm45600_gyro_parse_fifo(struct iio_dev *indio_dev);
+
+struct iio_dev *inv_icm45600_accel_init(struct inv_icm45600_state *st);
+
+int inv_icm45600_accel_parse_fifo(struct iio_dev *indio_dev);
+
+#endif
diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c
new file mode 100644
index 0000000000000000000000000000000000000000..3c046cad83474da43509295dd5542e40b7a0296a
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c
@@ -0,0 +1,902 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2025 Invensense, Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/common/inv_sensors_timestamp.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include "inv_icm45600_buffer.h"
+#include "inv_icm45600_temp.h"
+#include "inv_icm45600.h"
+
+#define INV_ICM45600_ACCEL_CHAN(_modifier, _index, _ext_info)		\
+	{								\
+		.type = IIO_ACCEL,					\
+		.modified = 1,						\
+		.channel2 = _modifier,					\
+		.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_type_available =			\
+			BIT(IIO_CHAN_INFO_SCALE) |			\
+			BIT(IIO_CHAN_INFO_CALIBBIAS),			\
+		.info_mask_shared_by_all =				\
+			BIT(IIO_CHAN_INFO_SAMP_FREQ),			\
+		.info_mask_shared_by_all_available =			\
+			BIT(IIO_CHAN_INFO_SAMP_FREQ),			\
+		.scan_index = _index,					\
+		.scan_type = {						\
+			.sign = 's',					\
+			.realbits = 16,					\
+			.storagebits = 16,				\
+			.endianness = IIO_LE,				\
+		},							\
+		.ext_info = _ext_info,					\
+	}
+
+enum inv_icm45600_accel_scan {
+	INV_ICM45600_ACCEL_SCAN_X,
+	INV_ICM45600_ACCEL_SCAN_Y,
+	INV_ICM45600_ACCEL_SCAN_Z,
+	INV_ICM45600_ACCEL_SCAN_TEMP,
+	INV_ICM45600_ACCEL_SCAN_TIMESTAMP,
+};
+
+static const char * const inv_icm45600_accel_power_mode_items[] = {
+	"low-noise",
+	"low-power",
+};
+static const int inv_icm45600_accel_power_mode_values[] = {
+	INV_ICM45600_SENSOR_MODE_LOW_NOISE,
+	INV_ICM45600_SENSOR_MODE_LOW_POWER,
+};
+
+static int inv_icm45600_accel_power_mode_set(struct iio_dev *indio_dev,
+					     const struct iio_chan_spec *chan,
+					     unsigned int idx)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
+	unsigned int val = 0;
+	int power_mode;
+
+	if (chan->type != IIO_ACCEL)
+		return -EINVAL;
+
+	if (idx >= ARRAY_SIZE(inv_icm45600_accel_power_mode_values))
+		return -EINVAL;
+
+	if (iio_buffer_enabled(indio_dev))
+		return -EBUSY;
+
+	power_mode = inv_icm45600_accel_power_mode_values[idx];
+
+	guard(mutex)(&st->lock);
+
+	/* prevent change if power mode is not supported by the ODR */
+	switch (power_mode) {
+	case INV_ICM45600_SENSOR_MODE_LOW_NOISE:
+		if (st->conf.accel.odr >= INV_ICM45600_ODR_6_25HZ_LP &&
+		    st->conf.accel.odr <= INV_ICM45600_ODR_1_5625HZ_LP)
+			return -EPERM;
+		break;
+	case INV_ICM45600_SENSOR_MODE_LOW_POWER:
+	default:
+		if (st->conf.accel.odr <= INV_ICM45600_ODR_800HZ_LN)
+			return -EPERM;
+		break;
+	}
+
+	accel_st->power_mode = power_mode;
+
+	if (accel_st->power_mode == INV_ICM45600_SENSOR_MODE_LOW_POWER)
+		val = INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL;
+
+	return regmap_update_bits(st->map, INV_ICM45600_REG_SMC_CONTROL_0,
+				INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL, val);
+}
+
+static int inv_icm45600_accel_power_mode_get(struct iio_dev *indio_dev,
+					     const struct iio_chan_spec *chan)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
+	unsigned int idx;
+	int power_mode;
+
+	if (chan->type != IIO_ACCEL)
+		return -EINVAL;
+
+	guard(mutex)(&st->lock);
+
+	/* if sensor is on, returns actual power mode and not configured one */
+	switch (st->conf.accel.mode) {
+	case INV_ICM45600_SENSOR_MODE_LOW_POWER:
+	case INV_ICM45600_SENSOR_MODE_LOW_NOISE:
+		power_mode = st->conf.accel.mode;
+		break;
+	default:
+		power_mode = accel_st->power_mode;
+		break;
+	}
+
+	for (idx = 0; idx < ARRAY_SIZE(inv_icm45600_accel_power_mode_values); ++idx) {
+		if (power_mode == inv_icm45600_accel_power_mode_values[idx])
+			break;
+	}
+	if (idx >= ARRAY_SIZE(inv_icm45600_accel_power_mode_values))
+		return -EINVAL;
+
+	return idx;
+}
+
+static const struct iio_enum inv_icm45600_accel_power_mode_enum = {
+	.items = inv_icm45600_accel_power_mode_items,
+	.num_items = ARRAY_SIZE(inv_icm45600_accel_power_mode_items),
+	.set = inv_icm45600_accel_power_mode_set,
+	.get = inv_icm45600_accel_power_mode_get,
+};
+
+static const struct iio_chan_spec_ext_info inv_icm45600_accel_ext_infos[] = {
+	IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm45600_get_mount_matrix),
+	IIO_ENUM_AVAILABLE("power_mode", IIO_SHARED_BY_TYPE,
+			   &inv_icm45600_accel_power_mode_enum),
+	IIO_ENUM("power_mode", IIO_SHARED_BY_TYPE,
+		 &inv_icm45600_accel_power_mode_enum),
+	{},
+};
+
+static const struct iio_chan_spec inv_icm45600_accel_channels[] = {
+	INV_ICM45600_ACCEL_CHAN(IIO_MOD_X, INV_ICM45600_ACCEL_SCAN_X,
+				inv_icm45600_accel_ext_infos),
+	INV_ICM45600_ACCEL_CHAN(IIO_MOD_Y, INV_ICM45600_ACCEL_SCAN_Y,
+				inv_icm45600_accel_ext_infos),
+	INV_ICM45600_ACCEL_CHAN(IIO_MOD_Z, INV_ICM45600_ACCEL_SCAN_Z,
+				inv_icm45600_accel_ext_infos),
+	INV_ICM45600_TEMP_CHAN(INV_ICM45600_ACCEL_SCAN_TEMP),
+	IIO_CHAN_SOFT_TIMESTAMP(INV_ICM45600_ACCEL_SCAN_TIMESTAMP),
+};
+
+/*
+ * IIO buffer data: size must be a power of 2 and timestamp aligned
+ * 16 bytes: 6 bytes acceleration, 2 bytes temperature, 8 bytes timestamp
+ */
+struct inv_icm45600_accel_buffer {
+	struct inv_icm45600_fifo_sensor_data accel;
+	int16_t temp;
+	aligned_s64 timestamp;
+};
+
+#define INV_ICM45600_SCAN_MASK_ACCEL_3AXIS				\
+	(BIT(INV_ICM45600_ACCEL_SCAN_X) |				\
+	BIT(INV_ICM45600_ACCEL_SCAN_Y) |				\
+	BIT(INV_ICM45600_ACCEL_SCAN_Z))
+
+#define INV_ICM45600_SCAN_MASK_TEMP	BIT(INV_ICM45600_ACCEL_SCAN_TEMP)
+
+static const unsigned long inv_icm45600_accel_scan_masks[] = {
+	/* 3-axis accel + temperature */
+	INV_ICM45600_SCAN_MASK_ACCEL_3AXIS | INV_ICM45600_SCAN_MASK_TEMP,
+	0,
+};
+
+/* enable accelerometer sensor and FIFO write */
+static int inv_icm45600_accel_update_scan_mode(struct iio_dev *indio_dev,
+					       const unsigned long *scan_mask)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
+	struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_INIT;
+	unsigned int fifo_en = 0;
+	unsigned int sleep_temp = 0;
+	unsigned int sleep_accel = 0;
+	unsigned int sleep;
+	int ret;
+
+	scoped_guard(mutex, &st->lock) {
+		if (*scan_mask & INV_ICM45600_SCAN_MASK_TEMP)
+			fifo_en |= INV_ICM45600_SENSOR_TEMP;
+
+		if (*scan_mask & INV_ICM45600_SCAN_MASK_ACCEL_3AXIS) {
+			/* enable accel sensor */
+			conf.mode = accel_st->power_mode;
+			ret = inv_icm45600_set_accel_conf(st, &conf, &sleep_accel);
+			if (ret)
+				break;
+			fifo_en |= INV_ICM45600_SENSOR_ACCEL;
+		}
+
+		/* update data FIFO write */
+		ret = inv_icm45600_buffer_set_fifo_en(st, fifo_en | st->fifo.en);
+	}
+
+	/* sleep maximum required time */
+	sleep = max(sleep_accel, sleep_temp);
+	if (sleep)
+		msleep(sleep);
+	return ret;
+}
+
+static int inv_icm45600_accel_read_sensor(struct iio_dev *indio_dev,
+					  struct iio_chan_spec const *chan,
+					  int16_t *val)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
+	struct device *dev = regmap_get_device(st->map);
+	struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_INIT;
+	unsigned int reg;
+	__le16 *data;
+	int ret;
+
+	if (chan->type != IIO_ACCEL)
+		return -EINVAL;
+
+	switch (chan->channel2) {
+	case IIO_MOD_X:
+		reg = INV_ICM45600_REG_ACCEL_DATA_X;
+		break;
+	case IIO_MOD_Y:
+		reg = INV_ICM45600_REG_ACCEL_DATA_Y;
+		break;
+	case IIO_MOD_Z:
+		reg = INV_ICM45600_REG_ACCEL_DATA_Z;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	pm_runtime_get_sync(dev);
+	scoped_guard(mutex, &st->lock) {
+		/* enable accel sensor */
+		conf.mode = accel_st->power_mode;
+		ret = inv_icm45600_set_accel_conf(st, &conf, NULL);
+		if (ret)
+			break;
+
+		/* read accel register data */
+		data = (__le16 *)&st->buffer[0];
+		ret = regmap_bulk_read(st->map, reg, data, sizeof(*data));
+		if (ret)
+			break;
+
+		*val = (int16_t)le16_to_cpup(data);
+		if (*val == INV_ICM45600_DATA_INVALID)
+			ret = -EINVAL;
+	}
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+	return ret;
+}
+
+/* IIO format int + nano */
+static const int inv_icm45600_accel_scale[] = {
+	/* +/- 16G => 0.004788403 m/s-2 */
+	[2 * INV_ICM45600_ACCEL_FS_16G] = 0,
+	[2 * INV_ICM45600_ACCEL_FS_16G + 1] = 4788403,
+	/* +/- 8G => 0.002394202 m/s-2 */
+	[2 * INV_ICM45600_ACCEL_FS_8G] = 0,
+	[2 * INV_ICM45600_ACCEL_FS_8G + 1] = 2394202,
+	/* +/- 4G => 0.001197101 m/s-2 */
+	[2 * INV_ICM45600_ACCEL_FS_4G] = 0,
+	[2 * INV_ICM45600_ACCEL_FS_4G + 1] = 1197101,
+	/* +/- 2G => 0.000598550 m/s-2 */
+	[2 * INV_ICM45600_ACCEL_FS_2G] = 0,
+	[2 * INV_ICM45600_ACCEL_FS_2G + 1] = 598550,
+};
+static const int inv_icm45686_accel_scale[] = {
+	/* +/- 32G => 0.009576806 m/s-2 */
+	[2 * INV_ICM45686_ACCEL_FS_32G] = 0,
+	[2 * INV_ICM45686_ACCEL_FS_32G + 1] = 9576806,
+	/* +/- 16G => 0.004788403 m/s-2 */
+	[2 * INV_ICM45686_ACCEL_FS_16G] = 0,
+	[2 * INV_ICM45686_ACCEL_FS_16G + 1] = 4788403,
+	/* +/- 8G => 0.002394202 m/s-2 */
+	[2 * INV_ICM45686_ACCEL_FS_8G] = 0,
+	[2 * INV_ICM45686_ACCEL_FS_8G + 1] = 2394202,
+	/* +/- 4G => 0.001197101 m/s-2 */
+	[2 * INV_ICM45686_ACCEL_FS_4G] = 0,
+	[2 * INV_ICM45686_ACCEL_FS_4G + 1] = 1197101,
+	/* +/- 2G => 0.000598550 m/s-2 */
+	[2 * INV_ICM45686_ACCEL_FS_2G] = 0,
+	[2 * INV_ICM45686_ACCEL_FS_2G + 1] = 598550,
+};
+
+static int inv_icm45600_accel_read_scale(struct iio_dev *indio_dev,
+					 int *val, int *val2)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
+	unsigned int idx;
+
+	idx = st->conf.accel.fs;
+
+	/* Full scale register starts at 1 for not High FSR parts */
+	if (accel_st->scales == inv_icm45600_accel_scale)
+		idx--;
+
+	*val = accel_st->scales[2 * idx];
+	*val2 = accel_st->scales[2 * idx + 1];
+	return IIO_VAL_INT_PLUS_NANO;
+}
+
+static int inv_icm45600_accel_write_scale(struct iio_dev *indio_dev,
+					  int val, int val2)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
+	struct device *dev = regmap_get_device(st->map);
+	unsigned int idx;
+	struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_INIT;
+	int ret;
+
+	for (idx = 0; idx < accel_st->scales_len; idx += 2) {
+		if (val == accel_st->scales[idx] &&
+		    val2 == accel_st->scales[idx + 1])
+			break;
+	}
+	if (idx >= accel_st->scales_len)
+		return -EINVAL;
+
+	conf.fs = idx / 2;
+
+	/* Full scale register starts at 1 for not High FSR parts */
+	if (accel_st->scales == inv_icm45600_accel_scale)
+		conf.fs++;
+
+	pm_runtime_get_sync(dev);
+	scoped_guard(mutex, &st->lock) {
+		ret = inv_icm45600_set_accel_conf(st, &conf, NULL);
+	}
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+
+	return ret;
+}
+
+/* IIO format int + micro */
+static const int inv_icm45600_accel_odr[] = {
+	/* 1.5625Hz */
+	1, 562500,
+	/* 3.125Hz */
+	3, 125000,
+	/* 6.25Hz */
+	6, 250000,
+	/* 12.5Hz */
+	12, 500000,
+	/* 25Hz */
+	25, 0,
+	/* 50Hz */
+	50, 0,
+	/* 100Hz */
+	100, 0,
+	/* 200Hz */
+	200, 0,
+	/* 400Hz */
+	400, 0,
+	/* 800Hz */
+	800, 0,
+	/* 1.6kHz */
+	1600, 0,
+	/* 3.2kHz */
+	3200, 0,
+	/* 6.4kHz */
+	6400, 0,
+};
+
+static const int inv_icm45600_accel_odr_conv[] = {
+	INV_ICM45600_ODR_1_5625HZ_LP,
+	INV_ICM45600_ODR_3_125HZ_LP,
+	INV_ICM45600_ODR_6_25HZ_LP,
+	INV_ICM45600_ODR_12_5HZ,
+	INV_ICM45600_ODR_25HZ,
+	INV_ICM45600_ODR_50HZ,
+	INV_ICM45600_ODR_100HZ,
+	INV_ICM45600_ODR_200HZ,
+	INV_ICM45600_ODR_400HZ,
+	INV_ICM45600_ODR_800HZ_LN,
+	INV_ICM45600_ODR_1600HZ_LN,
+	INV_ICM45600_ODR_3200HZ_LN,
+	INV_ICM45600_ODR_6400HZ_LN,
+};
+
+static int inv_icm45600_accel_read_odr(struct inv_icm45600_state *st,
+				       int *val, int *val2)
+{
+	unsigned int odr;
+	unsigned int i;
+
+	odr = st->conf.accel.odr;
+
+	for (i = 0; i < ARRAY_SIZE(inv_icm45600_accel_odr_conv); ++i) {
+		if (inv_icm45600_accel_odr_conv[i] == odr)
+			break;
+	}
+	if (i >= ARRAY_SIZE(inv_icm45600_accel_odr_conv))
+		return -EINVAL;
+
+	*val = inv_icm45600_accel_odr[2 * i];
+	*val2 = inv_icm45600_accel_odr[2 * i + 1];
+
+	return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int inv_icm45600_accel_write_odr(struct iio_dev *indio_dev,
+					int val, int val2)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
+	struct inv_sensors_timestamp *ts = &accel_st->ts;
+	struct device *dev = regmap_get_device(st->map);
+	unsigned int idx;
+	struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_INIT;
+	int ret;
+
+	for (idx = 0; idx < ARRAY_SIZE(inv_icm45600_accel_odr); idx += 2) {
+		if (val == inv_icm45600_accel_odr[idx] &&
+		    val2 == inv_icm45600_accel_odr[idx + 1])
+			break;
+	}
+	if (idx >= ARRAY_SIZE(inv_icm45600_accel_odr))
+		return -EINVAL;
+
+	conf.odr = inv_icm45600_accel_odr_conv[idx / 2];
+
+	pm_runtime_get_sync(dev);
+	scoped_guard(mutex, &st->lock) {
+		ret = inv_sensors_timestamp_update_odr(ts, inv_icm45600_odr_to_period(conf.odr),
+						iio_buffer_enabled(indio_dev));
+		if (ret)
+			break;
+
+		if (st->conf.accel.mode != INV_ICM45600_SENSOR_MODE_OFF)
+			conf.mode = accel_st->power_mode;
+		ret = inv_icm45600_set_accel_conf(st, &conf, NULL);
+		if (ret)
+			break;
+		inv_icm45600_buffer_update_fifo_period(st);
+		inv_icm45600_buffer_update_watermark(st);
+	}
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+
+	return ret;
+}
+
+/*
+ * Calibration bias values, IIO range format int + micro.
+ * Value is limited to +/-1g coded on 12 bits signed. Step is 0.5mg.
+ */
+static int inv_icm45600_accel_calibbias[] = {
+	-10, 42010,		/* min: -10.042010 m/s² */
+	0, 4903,		/* step: 0.004903 m/s² */
+	10, 37106,		/* max: 10.037106 m/s² */
+};
+
+static int inv_icm45600_accel_read_offset(struct inv_icm45600_state *st,
+					  struct iio_chan_spec const *chan,
+					  int *val, int *val2)
+{
+	struct device *dev = regmap_get_device(st->map);
+	int64_t val64;
+	int32_t bias;
+	unsigned int reg;
+	int16_t offset;
+	uint8_t data[2];
+	int ret;
+
+	if (chan->type != IIO_ACCEL)
+		return -EINVAL;
+
+	switch (chan->channel2) {
+	case IIO_MOD_X:
+		reg = INV_ICM45600_IPREG_SYS2_REG_24;
+		break;
+	case IIO_MOD_Y:
+		reg = INV_ICM45600_IPREG_SYS2_REG_32;
+		break;
+	case IIO_MOD_Z:
+		reg = INV_ICM45600_IPREG_SYS2_REG_40;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	pm_runtime_get_sync(dev);
+	scoped_guard(mutex, &st->lock) {
+		ret = regmap_bulk_read(st->map, reg, st->buffer, sizeof(data));
+		memcpy(data, st->buffer, sizeof(data));
+	}
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+	if (ret)
+		return ret;
+
+	/* 12 bits signed value */
+	switch (chan->channel2) {
+	case IIO_MOD_X:
+		offset = sign_extend32(((data[0] & 0xF0) << 4) | data[1], 11);
+		break;
+	case IIO_MOD_Y:
+		offset = sign_extend32(((data[1] & 0x0F) << 8) | data[0], 11);
+		break;
+	case IIO_MOD_Z:
+		offset = sign_extend32(((data[0] & 0xF0) << 4) | data[1], 11);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/*
+	 * convert raw offset to g then to m/s²
+	 * 12 bits signed raw step 0.5mg to g: 5 / 10000
+	 * g to m/s²: 9.806650
+	 * result in micro (1000000)
+	 * (offset * 5 * 9.806650 * 1000000) / 10000
+	 */
+	val64 = (int64_t)offset * 5LL * 9806650LL;
+	/* for rounding, add + or - divisor (10000) divided by 2 */
+	if (val64 >= 0)
+		val64 += 10000LL / 2LL;
+	else
+		val64 -= 10000LL / 2LL;
+	bias = div_s64(val64, 10000L);
+	*val = bias / 1000000L;
+	*val2 = bias % 1000000L;
+
+	return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int inv_icm45600_accel_write_offset(struct inv_icm45600_state *st,
+					   struct iio_chan_spec const *chan,
+					   int val, int val2)
+{
+	struct device *dev = regmap_get_device(st->map);
+	int64_t val64;
+	int32_t min, max;
+	unsigned int reg;
+	int16_t offset;
+	int ret;
+
+	if (chan->type != IIO_ACCEL)
+		return -EINVAL;
+
+	switch (chan->channel2) {
+	case IIO_MOD_X:
+		reg = INV_ICM45600_IPREG_SYS2_REG_24;
+		break;
+	case IIO_MOD_Y:
+		reg = INV_ICM45600_IPREG_SYS2_REG_32;
+		break;
+	case IIO_MOD_Z:
+		reg = INV_ICM45600_IPREG_SYS2_REG_40;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* inv_icm45600_accel_calibbias: min - step - max in micro */
+	min = inv_icm45600_accel_calibbias[0] * 1000000L +
+	      inv_icm45600_accel_calibbias[1];
+	max = inv_icm45600_accel_calibbias[4] * 1000000L +
+	      inv_icm45600_accel_calibbias[5];
+	val64 = (int64_t)val * 1000000LL + (int64_t)val2;
+	if (val64 < min || val64 > max)
+		return -EINVAL;
+
+	/*
+	 * convert m/s² to g then to raw value
+	 * m/s² to g: 1 / 9.806650
+	 * g to raw 14 bits signed, step 0.125mg: 1000000 / 125
+	 * val in micro (1000000)
+	 * val * 1000000 / (9.806650 * 1000000 * 125)
+	 */
+	val64 = val64 * 1000000LL;
+	/* for rounding, add + or - divisor (9806650 * 125) divided by 2 */
+	if (val64 >= 0)
+		val64 += 9806650 * 125 / 2;
+	else
+		val64 -= 9806650 * 125 / 2;
+	offset = div_s64(val64, 9806650 * 125);
+
+	/* clamp value limited to 14 bits signed */
+	if (offset < -8192)
+		offset = -8192;
+	else if (offset > 8191)
+		offset = 8191;
+
+	pm_runtime_get_sync(dev);
+	scoped_guard(mutex, &st->lock) {
+		ret = regmap_bulk_write(st->map, reg, st->buffer, 2);
+	}
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+	return ret;
+}
+
+static int inv_icm45600_accel_read_raw(struct iio_dev *indio_dev,
+				       struct iio_chan_spec const *chan,
+				       int *val, int *val2, long mask)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	int16_t data;
+	int ret;
+
+	switch (chan->type) {
+	case IIO_ACCEL:
+		break;
+	case IIO_TEMP:
+		return inv_icm45600_temp_read_raw(indio_dev, chan, val, val2, mask);
+	default:
+		return -EINVAL;
+	}
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (!iio_device_claim_direct(indio_dev))
+			return -EBUSY;
+		ret = inv_icm45600_accel_read_sensor(indio_dev, chan, &data);
+		iio_device_release_direct(indio_dev);
+		if (ret)
+			return ret;
+		*val = data;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		return inv_icm45600_accel_read_scale(indio_dev, val, val2);
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return inv_icm45600_accel_read_odr(st, val, val2);
+	case IIO_CHAN_INFO_CALIBBIAS:
+		return inv_icm45600_accel_read_offset(st, chan, val, val2);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int inv_icm45600_accel_read_avail(struct iio_dev *indio_dev,
+					 struct iio_chan_spec const *chan,
+					 const int **vals,
+					 int *type, int *length, long mask)
+{
+	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
+
+	if (chan->type != IIO_ACCEL)
+		return -EINVAL;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		*vals = accel_st->scales;
+		*type = IIO_VAL_INT_PLUS_NANO;
+		*length = accel_st->scales_len;
+		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		*vals = inv_icm45600_accel_odr;
+		*type = IIO_VAL_INT_PLUS_MICRO;
+		*length = ARRAY_SIZE(inv_icm45600_accel_odr);
+		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_CALIBBIAS:
+		*vals = inv_icm45600_accel_calibbias;
+		*type = IIO_VAL_INT_PLUS_MICRO;
+		return IIO_AVAIL_RANGE;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int inv_icm45600_accel_write_raw(struct iio_dev *indio_dev,
+					struct iio_chan_spec const *chan,
+					int val, int val2, long mask)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	int ret;
+
+	if (chan->type != IIO_ACCEL)
+		return -EINVAL;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		if (!iio_device_claim_direct(indio_dev))
+			return -EBUSY;
+		ret = inv_icm45600_accel_write_scale(indio_dev, val, val2);
+		iio_device_release_direct(indio_dev);
+		return ret;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return inv_icm45600_accel_write_odr(indio_dev, val, val2);
+	case IIO_CHAN_INFO_CALIBBIAS:
+		if (!iio_device_claim_direct(indio_dev))
+			return -EBUSY;
+		ret = inv_icm45600_accel_write_offset(st, chan, val, val2);
+		iio_device_release_direct(indio_dev);
+		return ret;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int inv_icm45600_accel_write_raw_get_fmt(struct iio_dev *indio_dev,
+						struct iio_chan_spec const *chan,
+						long mask)
+{
+	if (chan->type != IIO_ACCEL)
+		return -EINVAL;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		return IIO_VAL_INT_PLUS_NANO;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return IIO_VAL_INT_PLUS_MICRO;
+	case IIO_CHAN_INFO_CALIBBIAS:
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int inv_icm45600_accel_hwfifo_set_watermark(struct iio_dev *indio_dev,
+						   unsigned int val)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	st->fifo.watermark.accel = val;
+	ret = inv_icm45600_buffer_update_watermark(st);
+
+	return ret;
+}
+
+static int inv_icm45600_accel_hwfifo_flush(struct iio_dev *indio_dev,
+					   unsigned int count)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	int ret;
+
+	if (count == 0)
+		return 0;
+
+	guard(mutex)(&st->lock);
+
+	ret = inv_icm45600_buffer_hwfifo_flush(st, count);
+	if (!ret)
+		ret = st->fifo.nb.accel;
+
+	return ret;
+}
+
+static const struct iio_info inv_icm45600_accel_info = {
+	.read_raw = inv_icm45600_accel_read_raw,
+	.read_avail = inv_icm45600_accel_read_avail,
+	.write_raw = inv_icm45600_accel_write_raw,
+	.write_raw_get_fmt = inv_icm45600_accel_write_raw_get_fmt,
+	.debugfs_reg_access = inv_icm45600_debugfs_reg,
+	.update_scan_mode = inv_icm45600_accel_update_scan_mode,
+	.hwfifo_set_watermark = inv_icm45600_accel_hwfifo_set_watermark,
+	.hwfifo_flush_to_buffer = inv_icm45600_accel_hwfifo_flush,
+};
+
+struct iio_dev *inv_icm45600_accel_init(struct inv_icm45600_state *st)
+{
+	struct device *dev = regmap_get_device(st->map);
+	const char *name;
+	struct inv_icm45600_sensor_state *accel_st;
+	struct inv_sensors_timestamp_chip ts_chip;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	name = devm_kasprintf(dev, GFP_KERNEL, "%s-accel", st->name);
+	if (!name)
+		return ERR_PTR(-ENOMEM);
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*accel_st));
+	if (!indio_dev)
+		return ERR_PTR(-ENOMEM);
+	accel_st = iio_priv(indio_dev);
+
+	switch (st->chip) {
+	case INV_CHIP_ICM45686:
+	case INV_CHIP_ICM45688P:
+	case INV_CHIP_ICM45689:
+	case INV_CHIP_ICM45687:
+		accel_st->scales = inv_icm45686_accel_scale;
+		accel_st->scales_len = ARRAY_SIZE(inv_icm45686_accel_scale);
+		break;
+	default:
+		accel_st->scales = inv_icm45600_accel_scale;
+		accel_st->scales_len = ARRAY_SIZE(inv_icm45600_accel_scale);
+		/* Set Accel default FSR */
+		ret = regmap_update_bits(st->map, INV_ICM45600_REG_ACCEL_CONFIG0,
+					INV_ICM45600_ACCEL_CONFIG0_FS_MASK,
+					INV_ICM45600_ACCEL_CONFIG0_FS_16G);
+		if (ret)
+			return ERR_PTR(ret);
+		break;
+	}
+	/* low-power (LP) mode by default at init, no ULP mode */
+	accel_st->power_mode = INV_ICM45600_SENSOR_MODE_LOW_POWER;
+	ret = regmap_set_bits(st->map, INV_ICM45600_REG_SMC_CONTROL_0,
+			      INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL);
+	if (ret)
+		return ERR_PTR(ret);
+
+	/*
+	 * clock period is 32kHz (31250ns)
+	 * jitter is +/- 2% (20 per mille)
+	 */
+	ts_chip.clock_period = 31250;
+	ts_chip.jitter = 20;
+	ts_chip.init_period = inv_icm45600_odr_to_period(st->conf.accel.odr);
+	inv_sensors_timestamp_init(&accel_st->ts, &ts_chip);
+
+	iio_device_set_drvdata(indio_dev, st);
+	indio_dev->name = name;
+	indio_dev->info = &inv_icm45600_accel_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = inv_icm45600_accel_channels;
+	indio_dev->num_channels = ARRAY_SIZE(inv_icm45600_accel_channels);
+	indio_dev->available_scan_masks = inv_icm45600_accel_scan_masks;
+
+	ret = devm_iio_kfifo_buffer_setup(dev, indio_dev,
+					  &inv_icm45600_buffer_ops);
+	if (ret)
+		return ERR_PTR(ret);
+
+	ret = devm_iio_device_register(dev, indio_dev);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return indio_dev;
+}
+
+int inv_icm45600_accel_parse_fifo(struct iio_dev *indio_dev)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
+	struct inv_sensors_timestamp *ts = &accel_st->ts;
+	ssize_t i, size;
+	unsigned int no;
+	const void *accel, *gyro, *timestamp;
+	const int8_t *temp;
+	unsigned int odr;
+	int64_t ts_val;
+	struct inv_icm45600_accel_buffer buffer;
+
+	/* parse all fifo packets */
+	for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) {
+		size = inv_icm45600_fifo_decode_packet(&st->fifo.data[i],
+				&accel, &gyro, &temp, &timestamp, &odr);
+		/* quit if error or FIFO is empty */
+		if (size <= 0)
+			return size;
+
+		/* skip packet if no accel data or data is invalid */
+		if (accel == NULL || !inv_icm45600_fifo_is_data_valid(accel))
+			continue;
+
+		/* update odr */
+		if (odr & INV_ICM45600_SENSOR_ACCEL)
+			inv_sensors_timestamp_apply_odr(ts, st->fifo.period,
+							 st->fifo.nb.total, no);
+
+		/* buffer is copied to userspace, zeroing it to avoid any data leak */
+		memset(&buffer, 0, sizeof(buffer));
+		memcpy(&buffer.accel, accel, sizeof(buffer.accel));
+		/* convert 8 bits FIFO temperature in high resolution format */
+		buffer.temp = temp ? (*temp * 64) : 0;
+		ts_val = inv_sensors_timestamp_pop(ts);
+		iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val);
+	}
+
+	return 0;
+}
diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c
new file mode 100644
index 0000000000000000000000000000000000000000..66e9826005a9fecf5b154c0a1308a4c4f40c4edd
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c
@@ -0,0 +1,906 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2025 Invensense, Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#include "inv_icm45600_buffer.h"
+#include "inv_icm45600.h"
+
+static int inv_icm45600_ireg_read(struct regmap *map, unsigned int reg,
+				   unsigned int *data, size_t count)
+{
+	int ret;
+	uint8_t addr[2];
+	ssize_t i;
+
+	addr[0] = INV_ICM45600_REG_GET_BANK(reg);
+	addr[1] = INV_ICM45600_REG_GET_ADDR(reg);
+
+	/* Burst write address */
+	ret = regmap_bulk_write(map, INV_ICM45600_REG_IREG_ADDR, addr, 2);
+	udelay(INV_ICM45600_IREG_DELAY_US);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < count; i++) {
+		ret = regmap_read(map, INV_ICM45600_REG_IREG_DATA, &data[i]);
+		udelay(INV_ICM45600_IREG_DELAY_US);
+	}
+
+	return ret;
+}
+
+static int inv_icm45600_ireg_write(struct regmap *map, unsigned int reg,
+				   uint8_t *data, size_t count)
+{
+	int ret;
+	uint8_t addr_data0[3];
+	ssize_t i;
+
+	addr_data0[0] = INV_ICM45600_REG_GET_BANK(reg);
+	addr_data0[1] = INV_ICM45600_REG_GET_ADDR(reg);
+	addr_data0[2] = data[0];
+
+	/* Burst write address and first byte */
+	ret = regmap_bulk_write(map, INV_ICM45600_REG_IREG_ADDR, addr_data0, 3);
+	udelay(INV_ICM45600_IREG_DELAY_US);
+	if (ret)
+		return ret;
+
+	for (i = 1; i < count; i++) {
+		ret = regmap_write(map, INV_ICM45600_REG_IREG_DATA, data[i]);
+		udelay(INV_ICM45600_IREG_DELAY_US);
+	}
+
+	return ret;
+}
+
+static int inv_icm45600_read(void *context, const void *reg_buf, size_t reg_size,
+			  void *val_buf, size_t val_size)
+{
+	unsigned int reg = (unsigned int) be16_to_cpup(reg_buf);
+	struct regmap *map = context;
+
+	if (INV_ICM45600_REG_GET_BANK(reg) == 0)
+		return regmap_bulk_read(map, INV_ICM45600_REG_GET_ADDR(reg), val_buf,
+						val_size);
+
+	return inv_icm45600_ireg_read(map, reg, val_buf, val_size);
+}
+
+static int inv_icm45600_write(void *context, const void *data,
+				   size_t count)
+{
+	uint8_t *d = (uint8_t *)data;
+	unsigned int reg = (unsigned int) be16_to_cpup(data);
+	struct regmap *map = context;
+
+	if (INV_ICM45600_REG_GET_BANK(reg) == 0)
+		return regmap_bulk_write(map, INV_ICM45600_REG_GET_ADDR(reg),
+					d+2, count-2);
+
+	return inv_icm45600_ireg_write(map, reg, d+2, count-2);
+}
+
+
+static const struct regmap_bus inv_icm45600_regmap_bus = {
+	.read = inv_icm45600_read,
+	.write = inv_icm45600_write,
+};
+
+static const struct regmap_config inv_icm45600_regmap_config = {
+	.reg_bits = 16,
+	.val_bits = 8,
+};
+
+struct inv_icm45600_hw {
+	uint8_t whoami;
+	const char *name;
+	const struct inv_icm45600_conf *conf;
+};
+
+/* chip initial default configuration (default FS value is based on icm45686) */
+static const struct inv_icm45600_conf inv_icm45600_default_conf = {
+	.gyro = {
+		.mode = INV_ICM45600_SENSOR_MODE_OFF,
+		.fs = INV_ICM45686_GYRO_FS_2000DPS,
+		.odr = INV_ICM45600_ODR_800HZ_LN,
+		.filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X,
+	},
+	.accel = {
+		.mode = INV_ICM45600_SENSOR_MODE_OFF,
+		.fs = INV_ICM45686_ACCEL_FS_16G,
+		.odr = INV_ICM45600_ODR_800HZ_LN,
+		.filter = INV_ICM45600_ACCEL_LP_AVG_SEL_4X,
+	},
+};
+
+static const struct inv_icm45600_conf inv_icm45686_default_conf = {
+	.gyro = {
+		.mode = INV_ICM45600_SENSOR_MODE_OFF,
+		.fs = INV_ICM45686_GYRO_FS_4000DPS,
+		.odr = INV_ICM45600_ODR_800HZ_LN,
+		.filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X,
+	},
+	.accel = {
+		.mode = INV_ICM45600_SENSOR_MODE_OFF,
+		.fs = INV_ICM45686_ACCEL_FS_32G,
+		.odr = INV_ICM45600_ODR_800HZ_LN,
+		.filter = INV_ICM45600_ACCEL_LP_AVG_SEL_4X,
+	},
+};
+
+static const struct inv_icm45600_hw inv_icm45600_hw[INV_CHIP_NB] = {
+	[INV_CHIP_ICM45605] = {
+		.whoami = INV_ICM45600_WHOAMI_ICM45605,
+		.name = "icm45605",
+		.conf = &inv_icm45600_default_conf,
+	},
+	[INV_CHIP_ICM45686] = {
+		.whoami = INV_ICM45600_WHOAMI_ICM45686,
+		.name = "icm45686",
+		.conf = &inv_icm45686_default_conf,
+	},
+	[INV_CHIP_ICM45688P] = {
+		.whoami = INV_ICM45600_WHOAMI_ICM45688P,
+		.name = "icm45688p",
+		.conf = &inv_icm45686_default_conf,
+	},
+	[INV_CHIP_ICM45608] = {
+		.whoami = INV_ICM45600_WHOAMI_ICM45608,
+		.name = "icm45608",
+		.conf = &inv_icm45600_default_conf,
+	},
+	[INV_CHIP_ICM45634] = {
+		.whoami = INV_ICM45600_WHOAMI_ICM45634,
+		.name = "icm45634",
+		.conf = &inv_icm45600_default_conf,
+	},
+	[INV_CHIP_ICM45689] = {
+		.whoami = INV_ICM45600_WHOAMI_ICM45689,
+		.name = "icm45689",
+		.conf = &inv_icm45686_default_conf,
+	},
+	[INV_CHIP_ICM45606] = {
+		.whoami = INV_ICM45600_WHOAMI_ICM45606,
+		.name = "icm45606",
+		.conf = &inv_icm45600_default_conf,
+	},
+	[INV_CHIP_ICM45687] = {
+		.whoami = INV_ICM45600_WHOAMI_ICM45687,
+		.name = "icm45687",
+		.conf = &inv_icm45686_default_conf,
+	},
+};
+
+const struct iio_mount_matrix *
+inv_icm45600_get_mount_matrix(const struct iio_dev *indio_dev,
+			      const struct iio_chan_spec *chan)
+{
+	const struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+
+	return &st->orientation;
+}
+
+uint32_t inv_icm45600_odr_to_period(enum inv_icm45600_odr odr)
+{
+	static uint32_t odr_periods[INV_ICM45600_ODR_NB] = {
+		/* reserved values */
+		0, 0, 0,
+		/* 6.4kHz */
+		156250,
+		/* 3.2kHz */
+		312500,
+		/* 1.6kHz */
+		625000,
+		/* 800kHz */
+		1250000,
+		/* 400Hz */
+		2500000,
+		/* 200Hz */
+		5000000,
+		/* 100Hz */
+		10000000,
+		/* 50Hz */
+		20000000,
+		/* 25Hz */
+		40000000,
+		/* 12.5Hz */
+		80000000,
+		/* 6.25Hz */
+		160000000,
+		/* 3.125Hz */
+		320000000,
+		/* 1.5625Hz */
+		640000000,
+	};
+
+	return odr_periods[odr];
+}
+
+static int inv_icm45600_set_pwr_mgmt0(struct inv_icm45600_state *st,
+				      enum inv_icm45600_sensor_mode gyro,
+				      enum inv_icm45600_sensor_mode accel,
+				      unsigned int *sleep_ms)
+{
+	enum inv_icm45600_sensor_mode oldgyro = st->conf.gyro.mode;
+	enum inv_icm45600_sensor_mode oldaccel = st->conf.accel.mode;
+	unsigned int sleepval;
+	unsigned int val;
+	int ret;
+
+	/* if nothing changed, exit */
+	if (gyro == oldgyro && accel == oldaccel)
+		return 0;
+
+	val = INV_ICM45600_PWR_MGMT0_GYRO(gyro) |
+	      INV_ICM45600_PWR_MGMT0_ACCEL(accel);
+	ret = regmap_write(st->map, INV_ICM45600_REG_PWR_MGMT0, val);
+	if (ret)
+		return ret;
+
+	st->conf.gyro.mode = gyro;
+	st->conf.accel.mode = accel;
+
+	/* compute required wait time for sensors to stabilize */
+	sleepval = 0;
+
+	/* accel startup time */
+	if (accel != oldaccel && oldaccel == INV_ICM45600_SENSOR_MODE_OFF) {
+		if (sleepval < INV_ICM45600_ACCEL_STARTUP_TIME_MS)
+			sleepval = INV_ICM45600_ACCEL_STARTUP_TIME_MS;
+	}
+	if (gyro != oldgyro) {
+		/* gyro startup time */
+		if (oldgyro == INV_ICM45600_SENSOR_MODE_OFF) {
+			if (sleepval < INV_ICM45600_GYRO_STARTUP_TIME_MS)
+				sleepval = INV_ICM45600_GYRO_STARTUP_TIME_MS;
+		/* gyro stop time */
+		} else if (gyro == INV_ICM45600_SENSOR_MODE_OFF) {
+			if (sleepval < INV_ICM45600_GYRO_STOP_TIME_MS)
+				sleepval =  INV_ICM45600_GYRO_STOP_TIME_MS;
+		}
+	}
+
+	/* deferred sleep value if sleep pointer is provided or direct sleep */
+	if (sleep_ms)
+		*sleep_ms = sleepval;
+	else if (sleepval)
+		msleep(sleepval);
+
+	return 0;
+}
+
+int inv_icm45600_set_accel_conf(struct inv_icm45600_state *st,
+				struct inv_icm45600_sensor_conf *conf,
+				unsigned int *sleep_ms)
+{
+	struct inv_icm45600_sensor_conf *oldconf = &st->conf.accel;
+	unsigned int val;
+	int ret;
+
+	/* Sanitize missing values with current values */
+	if (conf->mode < 0)
+		conf->mode = oldconf->mode;
+	if (conf->fs < 0)
+		conf->fs = oldconf->fs;
+	if (conf->odr < 0)
+		conf->odr = oldconf->odr;
+	if (conf->filter < 0)
+		conf->filter = oldconf->filter;
+
+	/* force power mode against ODR when sensor is on */
+	switch (conf->mode) {
+	case INV_ICM45600_SENSOR_MODE_LOW_POWER:
+	case INV_ICM45600_SENSOR_MODE_LOW_NOISE:
+		if (conf->odr <= INV_ICM45600_ODR_800HZ_LN) {
+			conf->mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE;
+		} else if (conf->odr >= INV_ICM45600_ODR_6_25HZ_LP &&
+			   conf->odr <= INV_ICM45600_ODR_1_5625HZ_LP) {
+			conf->mode = INV_ICM45600_SENSOR_MODE_LOW_POWER;
+		}
+		break;
+	default:
+		break;
+	}
+
+	/* set ACCEL_CONFIG0 register (accel fullscale & odr) */
+	if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) {
+		val = INV_ICM45600_ACCEL_CONFIG0_FS(conf->fs) |
+		      INV_ICM45600_ACCEL_CONFIG0_ODR(conf->odr);
+		ret = regmap_write(st->map, INV_ICM45600_REG_ACCEL_CONFIG0, val);
+		if (ret)
+			return ret;
+		oldconf->fs = conf->fs;
+		oldconf->odr = conf->odr;
+	}
+
+	/* set ACCEL_LP_AVG_SEL register (accel low-power average filter) */
+	if (conf->filter != oldconf->filter) {
+		ret = regmap_write(st->map, INV_ICM45600_IPREG_SYS2_REG_129,
+			conf->filter);
+		if (ret)
+			return ret;
+		oldconf->filter = conf->filter;
+	}
+
+	/* set PWR_MGMT0 register (accel sensor mode) */
+	return inv_icm45600_set_pwr_mgmt0(st, st->conf.gyro.mode, conf->mode,
+					  sleep_ms);
+}
+
+int inv_icm45600_set_gyro_conf(struct inv_icm45600_state *st,
+			       struct inv_icm45600_sensor_conf *conf,
+			       unsigned int *sleep_ms)
+{
+	struct inv_icm45600_sensor_conf *oldconf = &st->conf.gyro;
+	unsigned int val;
+	int ret;
+
+	/* sanitize missing values with current values */
+	if (conf->mode < 0)
+		conf->mode = oldconf->mode;
+	if (conf->fs < 0)
+		conf->fs = oldconf->fs;
+	if (conf->odr < 0)
+		conf->odr = oldconf->odr;
+	if (conf->filter < 0)
+		conf->filter = oldconf->filter;
+
+	/* force power mode against ODR when sensor is on */
+	switch (conf->mode) {
+	case INV_ICM45600_SENSOR_MODE_LOW_POWER:
+		if (conf->odr != INV_ICM45600_ODR_400HZ)
+			conf->filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X;
+		else
+			conf->filter = INV_ICM45600_GYRO_LP_AVG_SEL_2X;
+		if (conf->odr <= INV_ICM45600_ODR_800HZ_LN)
+			conf->mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE;
+		break;
+	case INV_ICM45600_SENSOR_MODE_LOW_NOISE:
+		if (conf->odr >= INV_ICM45600_ODR_6_25HZ_LP &&
+			   conf->odr <= INV_ICM45600_ODR_1_5625HZ_LP) {
+			conf->mode = INV_ICM45600_SENSOR_MODE_LOW_POWER;
+			conf->filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X;
+		}
+		break;
+	default:
+		break;
+	}
+
+	/* set GYRO_CONFIG0 register (gyro fullscale & odr) */
+	if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) {
+		val = INV_ICM45600_GYRO_CONFIG0_FS(conf->fs) |
+		      INV_ICM45600_GYRO_CONFIG0_ODR(conf->odr);
+		ret = regmap_write(st->map, INV_ICM45600_REG_GYRO_CONFIG0, val);
+		if (ret)
+			return ret;
+		oldconf->fs = conf->fs;
+		oldconf->odr = conf->odr;
+	}
+
+	/* set GYRO_LP_AVG_SEL register (gyro low-power average filter) */
+	if (conf->filter != oldconf->filter) {
+		ret = regmap_update_bits(st->map, INV_ICM45600_IPREG_SYS1_REG_170,
+			INV_ICM45600_IPREG_SYS1_REG_170_MASK, conf->filter);
+		if (ret)
+			return ret;
+		oldconf->filter = conf->filter;
+	}
+
+	/* set PWR_MGMT0 register (gyro sensor mode) */
+	return inv_icm45600_set_pwr_mgmt0(st, conf->mode, st->conf.accel.mode,
+					  sleep_ms);
+
+	return 0;
+}
+
+int inv_icm45600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg,
+			     unsigned int writeval, unsigned int *readval)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	if (readval)
+		ret = regmap_read(st->map, reg, readval);
+	else
+		ret = regmap_write(st->map, reg, writeval);
+
+	return ret;
+}
+
+static int inv_icm45600_set_conf(struct inv_icm45600_state *st,
+				 const struct inv_icm45600_conf *conf)
+{
+	unsigned int val;
+	int ret;
+
+	/* set PWR_MGMT0 register (gyro & accel sensor mode, temp enabled) */
+	val = INV_ICM45600_PWR_MGMT0_GYRO(conf->gyro.mode) |
+	      INV_ICM45600_PWR_MGMT0_ACCEL(conf->accel.mode);
+	ret = regmap_write(st->map, INV_ICM45600_REG_PWR_MGMT0, val);
+	if (ret)
+		return ret;
+
+	/* set GYRO_CONFIG0 register (gyro fullscale & odr) */
+	val = INV_ICM45600_GYRO_CONFIG0_FS(conf->gyro.fs) |
+	      INV_ICM45600_GYRO_CONFIG0_ODR(conf->gyro.odr);
+	ret = regmap_write(st->map, INV_ICM45600_REG_GYRO_CONFIG0, val);
+	if (ret)
+		return ret;
+
+	/* set ACCEL_CONFIG0 register (accel fullscale & odr) */
+	val = INV_ICM45600_ACCEL_CONFIG0_FS(conf->accel.fs) |
+	      INV_ICM45600_ACCEL_CONFIG0_ODR(conf->accel.odr);
+	ret = regmap_write(st->map, INV_ICM45600_REG_ACCEL_CONFIG0, val);
+	if (ret)
+		return ret;
+
+	/* update internal conf */
+	st->conf = *conf;
+
+	return 0;
+}
+
+/**
+ *  inv_icm45600_setup() - check and setup chip
+ *  @st:	driver internal state
+ *  @bus_setup:	callback for setting up bus specific registers
+ *
+ *  Returns 0 on success, a negative error code otherwise.
+ */
+static int inv_icm45600_setup(struct inv_icm45600_state *st, bool reset,
+			      inv_icm45600_bus_setup bus_setup)
+{
+	const struct inv_icm45600_hw *hw = &inv_icm45600_hw[st->chip];
+	const struct device *dev = regmap_get_device(st->map);
+	unsigned int val;
+	int ret;
+
+	/* set chip bus configuration if specified */
+	if (bus_setup) {
+		ret = bus_setup(st);
+		if (ret)
+			return ret;
+	}
+
+	/* check chip self-identification value */
+	ret = regmap_read(st->map, INV_ICM45600_REG_WHOAMI, &val);
+	if (ret)
+		return ret;
+	if (val != hw->whoami) {
+		dev_err(dev, "invalid whoami %#02x expected %#02x (%s)\n",
+			val, hw->whoami, hw->name);
+		return -ENODEV;
+	}
+	st->name = hw->name;
+
+	if (reset) {
+		/* reset to make sure previous state are not there */
+		ret = regmap_write(st->map, INV_ICM45600_REG_MISC2,
+				INV_ICM45600_MISC2_SOFT_RESET);
+		if (ret)
+			return ret;
+		msleep(INV_ICM45600_RESET_TIME_MS);
+
+		if (bus_setup) {
+			ret = bus_setup(st);
+			if (ret)
+				return ret;
+		}
+
+		ret = regmap_read(st->map, INV_ICM45600_REG_INT_STATUS, &val);
+		if (ret)
+			return ret;
+		if (!(val & INV_ICM45600_INT_STATUS_RESET_DONE)) {
+			dev_err(dev, "reset error, reset done bit not set\n");
+			return -ENODEV;
+		}
+	}
+
+	return inv_icm45600_set_conf(st, hw->conf);
+}
+
+static irqreturn_t inv_icm45600_irq_timestamp(int irq, void *_data)
+{
+	struct inv_icm45600_state *st = _data;
+
+	st->timestamp.gyro = iio_get_time_ns(st->indio_gyro);
+	st->timestamp.accel = iio_get_time_ns(st->indio_accel);
+
+	return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t inv_icm45600_irq_handler(int irq, void *_data)
+{
+	struct inv_icm45600_state *st = _data;
+	struct device *dev = regmap_get_device(st->map);
+	unsigned int mask, status;
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	ret = regmap_read(st->map, INV_ICM45600_REG_INT_STATUS, &status);
+	if (ret)
+		return IRQ_HANDLED;
+
+	/* read FIFO data */
+	mask = INV_ICM45600_INT_STATUS_FIFO_THS | INV_ICM45600_INT_STATUS_FIFO_FULL;
+	if (status & mask) {
+		ret = inv_icm45600_buffer_fifo_read(st, 0);
+		if (ret) {
+			dev_err(dev, "FIFO read error %d\n", ret);
+			return IRQ_HANDLED;
+		}
+		ret = inv_icm45600_buffer_fifo_parse(st);
+		if (ret)
+			dev_err(dev, "FIFO parsing error %d\n", ret);
+	}
+
+	/* FIFO full warning */
+	if (status & INV_ICM45600_INT_STATUS_FIFO_FULL)
+		dev_warn(dev, "FIFO full possible data lost!\n");
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * inv_icm45600_irq_init() - initialize int pin and interrupt handler
+ * @st:		driver internal state
+ * @irq:	irq number
+ * @irq_type:	irq trigger type
+ * @open_drain:	true if irq is open drain, false for push-pull
+ *
+ * Returns 0 on success, a negative error code otherwise.
+ */
+static int inv_icm45600_irq_init(struct inv_icm45600_state *st, int irq,
+				 int irq_type, bool open_drain)
+{
+	struct device *dev = regmap_get_device(st->map);
+	unsigned int val;
+	int ret;
+
+	/* configure INT1 interrupt: default is active low on edge */
+	switch (irq_type) {
+	case IRQF_TRIGGER_RISING:
+	case IRQF_TRIGGER_HIGH:
+		val = INV_ICM45600_INT1_CONFIG2_ACTIVE_HIGH;
+		break;
+	default:
+		val = INV_ICM45600_INT1_CONFIG2_ACTIVE_LOW;
+		break;
+	}
+
+	switch (irq_type) {
+	case IRQF_TRIGGER_LOW:
+	case IRQF_TRIGGER_HIGH:
+		val |= INV_ICM45600_INT1_CONFIG2_LATCHED;
+		break;
+	default:
+		break;
+	}
+
+	if (!open_drain)
+		val |= INV_ICM45600_INT1_CONFIG2_PUSH_PULL;
+
+	ret = regmap_write(st->map, INV_ICM45600_REG_INT1_CONFIG2, val);
+	if (ret)
+		return ret;
+
+	irq_type |= IRQF_ONESHOT;
+	return devm_request_threaded_irq(dev, irq, inv_icm45600_irq_timestamp,
+					 inv_icm45600_irq_handler, irq_type,
+					 "inv_icm45600", st);
+}
+
+static int inv_icm45600_timestamp_setup(struct inv_icm45600_state *st)
+{
+	/* enable timestamp */
+	return regmap_set_bits(st->map, INV_ICM45600_REG_SMC_CONTROL_0,
+					INV_ICM45600_SMC_CONTROL_0_TMST_EN);
+}
+
+static int inv_icm45600_enable_regulator_vddio(struct inv_icm45600_state *st)
+{
+	int ret;
+
+	ret = regulator_enable(st->vddio_supply);
+	if (ret)
+		return ret;
+
+	/* wait a little for supply ramp */
+	usleep_range(3000, 4000);
+
+	return 0;
+}
+
+static void inv_icm45600_disable_vdd_reg(void *_data)
+{
+	struct inv_icm45600_state *st = _data;
+	const struct device *dev = regmap_get_device(st->map);
+	int ret;
+
+	ret = regulator_disable(st->vdd_supply);
+	if (ret)
+		dev_err(dev, "failed to disable vdd error %d\n", ret);
+}
+
+static void inv_icm45600_disable_vddio_reg(void *_data)
+{
+	struct inv_icm45600_state *st = _data;
+	const struct device *dev = regmap_get_device(st->map);
+	int ret;
+
+	ret = regulator_disable(st->vddio_supply);
+	if (ret)
+		dev_err(dev, "failed to disable vddio error %d\n", ret);
+}
+
+static void inv_icm45600_disable_pm(void *_data)
+{
+	struct device *dev = _data;
+
+	pm_runtime_put_sync(dev);
+	pm_runtime_disable(dev);
+}
+
+int inv_icm45600_core_probe(struct regmap *regmap, int chip, bool reset,
+			    inv_icm45600_bus_setup bus_setup)
+{
+	struct device *dev = regmap_get_device(regmap);
+	struct fwnode_handle *fwnode;
+	struct inv_icm45600_state *st;
+	struct regmap *regmap_custom;
+	int irq, irq_type;
+	bool open_drain;
+	int ret;
+
+	if (chip <= INV_CHIP_INVALID || chip >= INV_CHIP_NB) {
+		dev_err(dev, "invalid chip = %d\n", chip);
+		return -ENODEV;
+	}
+
+	/* get INT1 only supported interrupt */
+	fwnode = dev_fwnode(dev);
+	if (!fwnode)
+		return -ENODEV;
+	irq = fwnode_irq_get_byname(fwnode, "INT1");
+	if (irq < 0) {
+		if (irq != -EPROBE_DEFER)
+			dev_err(dev, "error missing INT1 interrupt\n");
+		return irq;
+	}
+
+	irq_type = irq_get_trigger_type(irq);
+	if (!irq_type)
+		irq_type = IRQF_TRIGGER_FALLING;
+
+	open_drain = device_property_read_bool(dev, "drive-open-drain");
+
+	regmap_custom = devm_regmap_init(dev, &inv_icm45600_regmap_bus,
+					 regmap, &inv_icm45600_regmap_config);
+	if (IS_ERR(regmap_custom)) {
+		dev_err(dev, "Failed to register icm45600 regmap %ld\n", PTR_ERR(regmap_custom));
+		return PTR_ERR(regmap_custom);
+	}
+
+	st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
+	if (!st)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, st);
+	mutex_init(&st->lock);
+	st->chip = chip;
+	st->map = regmap_custom;
+
+	ret = iio_read_mount_matrix(dev, &st->orientation);
+	if (ret) {
+		dev_err(dev, "failed to retrieve mounting matrix %d\n", ret);
+		return ret;
+	}
+
+	st->vdd_supply = devm_regulator_get(dev, "vdd");
+	if (IS_ERR(st->vdd_supply))
+		return PTR_ERR(st->vdd_supply);
+
+	st->vddio_supply = devm_regulator_get(dev, "vddio");
+	if (IS_ERR(st->vddio_supply))
+		return PTR_ERR(st->vddio_supply);
+
+	ret = regulator_enable(st->vdd_supply);
+	if (ret)
+		return ret;
+	msleep(INV_ICM45600_POWER_UP_TIME_MS);
+
+	ret = devm_add_action_or_reset(dev, inv_icm45600_disable_vdd_reg, st);
+	if (ret)
+		return ret;
+
+	ret = inv_icm45600_enable_regulator_vddio(st);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action_or_reset(dev, inv_icm45600_disable_vddio_reg, st);
+	if (ret)
+		return ret;
+
+	/* setup chip registers */
+	ret = inv_icm45600_setup(st, reset, bus_setup);
+	if (ret)
+		return ret;
+
+	ret = inv_icm45600_timestamp_setup(st);
+	if (ret)
+		return ret;
+
+	ret = inv_icm45600_buffer_init(st);
+	if (ret)
+		return ret;
+
+	st->indio_gyro = inv_icm45600_gyro_init(st);
+	if (IS_ERR(st->indio_gyro))
+		return PTR_ERR(st->indio_gyro);
+
+	st->indio_accel = inv_icm45600_accel_init(st);
+	if (IS_ERR(st->indio_accel))
+		return PTR_ERR(st->indio_accel);
+
+	ret = inv_icm45600_irq_init(st, irq, irq_type, open_drain);
+	if (ret)
+		return ret;
+
+	/* setup runtime power management */
+	ret = pm_runtime_set_active(dev);
+	if (ret)
+		return ret;
+	pm_runtime_get_noresume(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_set_autosuspend_delay(dev, INV_ICM45600_SUSPEND_DELAY_MS);
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_put(dev);
+
+	return devm_add_action_or_reset(dev, inv_icm45600_disable_pm, dev);
+}
+EXPORT_SYMBOL_NS_GPL(inv_icm45600_core_probe, "IIO_ICM45600");
+
+/*
+ * Suspend saves sensors state and turns everything off.
+ * Check first if runtime suspend has not already done the job.
+ */
+static int inv_icm45600_suspend(struct device *dev)
+{
+	struct inv_icm45600_state *st = dev_get_drvdata(dev);
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	st->suspended.gyro = st->conf.gyro.mode;
+	st->suspended.accel = st->conf.accel.mode;
+	if (pm_runtime_suspended(dev))
+		return 0;
+
+	/* disable FIFO data streaming */
+	if (st->fifo.on) {
+		ret = regmap_clear_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3,
+					INV_ICM45600_FIFO_CONFIG3_IF_EN);
+		if (ret)
+			return ret;
+		ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0,
+					 INV_ICM45600_FIFO_CONFIG0_MODE_MASK,
+					 INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS);
+		if (ret)
+			return ret;
+	}
+
+	ret = inv_icm45600_set_pwr_mgmt0(st, INV_ICM45600_SENSOR_MODE_OFF,
+					 INV_ICM45600_SENSOR_MODE_OFF, NULL);
+	if (ret)
+		return ret;
+
+	regulator_disable(st->vddio_supply);
+
+	return ret;
+}
+
+/*
+ * System resume gets the system back on and restores the sensors state.
+ * Manually put runtime power management in system active state.
+ */
+static int inv_icm45600_resume(struct device *dev)
+{
+	struct inv_icm45600_state *st = dev_get_drvdata(dev);
+	struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro);
+	struct inv_icm45600_sensor_state *accel_st = iio_priv(st->indio_accel);
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	ret = inv_icm45600_enable_regulator_vddio(st);
+	if (ret)
+		return ret;
+
+	pm_runtime_disable(dev);
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+
+	/* restore sensors state */
+	ret = inv_icm45600_set_pwr_mgmt0(st, st->suspended.gyro,
+					 st->suspended.accel, NULL);
+	if (ret)
+		return ret;
+
+	/* restore FIFO data streaming */
+	if (st->fifo.on) {
+		inv_sensors_timestamp_reset(&gyro_st->ts);
+		inv_sensors_timestamp_reset(&accel_st->ts);
+		ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0,
+					 INV_ICM45600_FIFO_CONFIG0_MODE_MASK,
+					 INV_ICM45600_FIFO_CONFIG0_MODE_STREAM);
+		if (ret)
+			return ret;
+		ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3,
+				      INV_ICM45600_FIFO_CONFIG3_IF_EN);
+	}
+
+	return ret;
+}
+
+/* Runtime suspend will turn off sensors that are enabled by iio devices. */
+static int inv_icm45600_runtime_suspend(struct device *dev)
+{
+	struct inv_icm45600_state *st = dev_get_drvdata(dev);
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	/* disable all sensors */
+	ret = inv_icm45600_set_pwr_mgmt0(st, INV_ICM45600_SENSOR_MODE_OFF,
+					 INV_ICM45600_SENSOR_MODE_OFF, NULL);
+	if (ret)
+		return ret;
+
+	regulator_disable(st->vddio_supply);
+
+	return ret;
+}
+
+/* Sensors are enabled by iio devices, no need to turn them back on here. */
+static int inv_icm45600_runtime_resume(struct device *dev)
+{
+	struct inv_icm45600_state *st = dev_get_drvdata(dev);
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	ret = inv_icm45600_enable_regulator_vddio(st);
+
+	return ret;
+}
+
+
+EXPORT_NS_GPL_DEV_PM_OPS(inv_icm45600_pm_ops, IIO_ICM45600) = {
+	SET_SYSTEM_SLEEP_PM_OPS(inv_icm45600_suspend, inv_icm45600_resume)
+	SET_RUNTIME_PM_OPS(inv_icm45600_runtime_suspend,
+			   inv_icm45600_runtime_resume, NULL)
+};
+
+MODULE_AUTHOR("InvenSense, Inc.");
+MODULE_DESCRIPTION("InvenSense ICM-456xx device driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_INV_SENSORS_TIMESTAMP");
diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c
new file mode 100644
index 0000000000000000000000000000000000000000..4ada93f990c1ffdc1e0a00ae7c78e03c09d1c682
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c
@@ -0,0 +1,919 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2025 Invensense, Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/common/inv_sensors_timestamp.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include "inv_icm45600_buffer.h"
+#include "inv_icm45600_temp.h"
+#include "inv_icm45600.h"
+
+
+#define INV_ICM45600_GYRO_CHAN(_modifier, _index, _ext_info)		\
+	{								\
+		.type = IIO_ANGL_VEL,					\
+		.modified = 1,						\
+		.channel2 = _modifier,					\
+		.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_type_available =			\
+			BIT(IIO_CHAN_INFO_SCALE) |			\
+			BIT(IIO_CHAN_INFO_CALIBBIAS),			\
+		.info_mask_shared_by_all =				\
+			BIT(IIO_CHAN_INFO_SAMP_FREQ),			\
+		.info_mask_shared_by_all_available =			\
+			BIT(IIO_CHAN_INFO_SAMP_FREQ),			\
+		.scan_index = _index,					\
+		.scan_type = {						\
+			.sign = 's',					\
+			.realbits = 16,					\
+			.storagebits = 16,				\
+			.endianness = IIO_LE,				\
+		},							\
+		.ext_info = _ext_info,					\
+	}
+
+enum inv_icm45600_gyro_scan {
+	INV_ICM45600_GYRO_SCAN_X,
+	INV_ICM45600_GYRO_SCAN_Y,
+	INV_ICM45600_GYRO_SCAN_Z,
+	INV_ICM45600_GYRO_SCAN_TEMP,
+	INV_ICM45600_GYRO_SCAN_TIMESTAMP,
+};
+
+
+static const char * const inv_icm45600_gyro_power_mode_items[] = {
+	"low-noise",
+	"low-power",
+};
+static const int inv_icm45600_gyro_power_mode_values[] = {
+	INV_ICM45600_SENSOR_MODE_LOW_NOISE,
+	INV_ICM45600_SENSOR_MODE_LOW_POWER,
+};
+
+static int inv_icm45600_gyro_power_mode_set(struct iio_dev *indio_dev,
+					     const struct iio_chan_spec *chan,
+					     unsigned int idx)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
+	int power_mode;
+
+	if (chan->type != IIO_ANGL_VEL)
+		return -EINVAL;
+
+	if (idx >= ARRAY_SIZE(inv_icm45600_gyro_power_mode_values))
+		return -EINVAL;
+
+	if (iio_buffer_enabled(indio_dev))
+		return -EBUSY;
+
+	power_mode = inv_icm45600_gyro_power_mode_values[idx];
+
+	guard(mutex)(&st->lock);
+
+	/* prevent change if power mode is not supported by the ODR */
+	switch (power_mode) {
+	case INV_ICM45600_SENSOR_MODE_LOW_NOISE:
+		if (st->conf.gyro.odr >= INV_ICM45600_ODR_6_25HZ_LP &&
+		    st->conf.gyro.odr <= INV_ICM45600_ODR_1_5625HZ_LP)
+			return -EPERM;
+		break;
+	case INV_ICM45600_SENSOR_MODE_LOW_POWER:
+	default:
+		if (st->conf.gyro.odr <= INV_ICM45600_ODR_800HZ_LN)
+			return -EPERM;
+		break;
+	}
+
+	gyro_st->power_mode = power_mode;
+
+	return 0;
+}
+
+static int inv_icm45600_gyro_power_mode_get(struct iio_dev *indio_dev,
+					     const struct iio_chan_spec *chan)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
+	unsigned int idx;
+	int power_mode;
+
+	if (chan->type != IIO_ANGL_VEL)
+		return -EINVAL;
+
+	guard(mutex)(&st->lock);
+
+	/* if sensor is on, returns actual power mode and not configured one */
+	switch (st->conf.gyro.mode) {
+	case INV_ICM45600_SENSOR_MODE_LOW_POWER:
+	case INV_ICM45600_SENSOR_MODE_LOW_NOISE:
+		power_mode = st->conf.gyro.mode;
+		break;
+	default:
+		power_mode = gyro_st->power_mode;
+		break;
+	}
+
+	for (idx = 0; idx < ARRAY_SIZE(inv_icm45600_gyro_power_mode_values); ++idx) {
+		if (power_mode == inv_icm45600_gyro_power_mode_values[idx])
+			break;
+	}
+	if (idx >= ARRAY_SIZE(inv_icm45600_gyro_power_mode_values))
+		return -EINVAL;
+
+	return idx;
+}
+
+static const struct iio_enum inv_icm45600_gyro_power_mode_enum = {
+	.items = inv_icm45600_gyro_power_mode_items,
+	.num_items = ARRAY_SIZE(inv_icm45600_gyro_power_mode_items),
+	.set = inv_icm45600_gyro_power_mode_set,
+	.get = inv_icm45600_gyro_power_mode_get,
+};
+
+static const struct iio_chan_spec_ext_info inv_icm45600_gyro_ext_infos[] = {
+	IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm45600_get_mount_matrix),
+	IIO_ENUM_AVAILABLE("power_mode", IIO_SHARED_BY_TYPE,
+			   &inv_icm45600_gyro_power_mode_enum),
+	IIO_ENUM("power_mode", IIO_SHARED_BY_TYPE,
+		 &inv_icm45600_gyro_power_mode_enum),
+	{},
+};
+
+static const struct iio_chan_spec inv_icm45600_gyro_channels[] = {
+	INV_ICM45600_GYRO_CHAN(IIO_MOD_X, INV_ICM45600_GYRO_SCAN_X,
+			       inv_icm45600_gyro_ext_infos),
+	INV_ICM45600_GYRO_CHAN(IIO_MOD_Y, INV_ICM45600_GYRO_SCAN_Y,
+			       inv_icm45600_gyro_ext_infos),
+	INV_ICM45600_GYRO_CHAN(IIO_MOD_Z, INV_ICM45600_GYRO_SCAN_Z,
+			       inv_icm45600_gyro_ext_infos),
+	INV_ICM45600_TEMP_CHAN(INV_ICM45600_GYRO_SCAN_TEMP),
+	IIO_CHAN_SOFT_TIMESTAMP(INV_ICM45600_GYRO_SCAN_TIMESTAMP),
+};
+
+/*
+ * IIO buffer data: size must be a power of 2 and timestamp aligned
+ * 16 bytes: 6 bytes angular velocity, 2 bytes temperature, 8 bytes timestamp
+ */
+struct inv_icm45600_gyro_buffer {
+	struct inv_icm45600_fifo_sensor_data gyro;
+	int16_t temp;
+	aligned_s64 timestamp;
+};
+
+#define INV_ICM45600_SCAN_MASK_GYRO_3AXIS				\
+	(BIT(INV_ICM45600_GYRO_SCAN_X) |				\
+	BIT(INV_ICM45600_GYRO_SCAN_Y) |					\
+	BIT(INV_ICM45600_GYRO_SCAN_Z))
+
+#define INV_ICM45600_SCAN_MASK_TEMP	BIT(INV_ICM45600_GYRO_SCAN_TEMP)
+
+static const unsigned long inv_icm45600_gyro_scan_masks[] = {
+	/* 3-axis gyro + temperature */
+	INV_ICM45600_SCAN_MASK_GYRO_3AXIS | INV_ICM45600_SCAN_MASK_TEMP,
+	0,
+};
+
+/* enable gyroscope sensor and FIFO write */
+static int inv_icm45600_gyro_update_scan_mode(struct iio_dev *indio_dev,
+					      const unsigned long *scan_mask)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
+	struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_INIT;
+	unsigned int fifo_en = 0;
+	unsigned int sleep_gyro = 0;
+	unsigned int sleep_temp = 0;
+	unsigned int sleep;
+	int ret;
+
+	scoped_guard(mutex, &st->lock) {
+		if (*scan_mask & INV_ICM45600_SCAN_MASK_TEMP)
+			fifo_en |= INV_ICM45600_SENSOR_TEMP;
+
+		if (*scan_mask & INV_ICM45600_SCAN_MASK_GYRO_3AXIS) {
+			/* enable gyro sensor */
+			conf.mode = gyro_st->power_mode;
+			ret = inv_icm45600_set_gyro_conf(st, &conf, &sleep_gyro);
+			if (ret)
+				break;
+			fifo_en |= INV_ICM45600_SENSOR_GYRO;
+		}
+		/* update data FIFO write */
+		ret = inv_icm45600_buffer_set_fifo_en(st, fifo_en | st->fifo.en);
+	}
+	/* sleep maximum required time */
+	sleep = max(sleep_gyro, sleep_temp);
+	if (sleep)
+		msleep(sleep);
+	return ret;
+}
+
+static int inv_icm45600_gyro_read_sensor(struct iio_dev *indio_dev,
+					 struct iio_chan_spec const *chan,
+					 int16_t *val)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
+	struct device *dev = regmap_get_device(st->map);
+	struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_INIT;
+	unsigned int reg;
+	__le16 *data;
+	int ret;
+
+	if (chan->type != IIO_ANGL_VEL)
+		return -EINVAL;
+
+	switch (chan->channel2) {
+	case IIO_MOD_X:
+		reg = INV_ICM45600_REG_GYRO_DATA_X;
+		break;
+	case IIO_MOD_Y:
+		reg = INV_ICM45600_REG_GYRO_DATA_Y;
+		break;
+	case IIO_MOD_Z:
+		reg = INV_ICM45600_REG_GYRO_DATA_Z;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	pm_runtime_get_sync(dev);
+	scoped_guard(mutex, &st->lock) {
+		/* enable gyro sensor */
+		conf.mode = gyro_st->power_mode;
+		ret = inv_icm45600_set_gyro_conf(st, &conf, NULL);
+		if (ret)
+			break;
+
+		/* read gyro register data */
+		data = (__le16 *)&st->buffer[0];
+		ret = regmap_bulk_read(st->map, reg, data, sizeof(*data));
+		if (ret)
+			break;
+
+		*val = (int16_t)le16_to_cpup(data);
+		if (*val == INV_ICM45600_DATA_INVALID)
+			ret = -EINVAL;
+	}
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+	return ret;
+}
+
+/* IIO format int + nano */
+static const int inv_icm45600_gyro_scale[] = {
+	/* +/- 2000dps => 0.001065264 rad/s */
+	[2 * INV_ICM45600_GYRO_FS_2000DPS] = 0,
+	[2 * INV_ICM45600_GYRO_FS_2000DPS + 1] = 1065264,
+	/* +/- 1000dps => 0.000532632 rad/s */
+	[2 * INV_ICM45600_GYRO_FS_1000DPS] = 0,
+	[2 * INV_ICM45600_GYRO_FS_1000DPS + 1] = 532632,
+	/* +/- 500dps => 0.000266316 rad/s */
+	[2 * INV_ICM45600_GYRO_FS_500DPS] = 0,
+	[2 * INV_ICM45600_GYRO_FS_500DPS + 1] = 266316,
+	/* +/- 250dps => 0.000133158 rad/s */
+	[2 * INV_ICM45600_GYRO_FS_250DPS] = 0,
+	[2 * INV_ICM45600_GYRO_FS_250DPS + 1] = 133158,
+	/* +/- 125dps => 0.000066579 rad/s */
+	[2 * INV_ICM45600_GYRO_FS_125DPS] = 0,
+	[2 * INV_ICM45600_GYRO_FS_125DPS + 1] = 66579,
+	/* +/- 62.5dps => 0.000033290 rad/s */
+	[2 * INV_ICM45600_GYRO_FS_62_5DPS] = 0,
+	[2 * INV_ICM45600_GYRO_FS_62_5DPS + 1] = 33290,
+	/* +/- 31.25dps => 0.000016645 rad/s */
+	[2 * INV_ICM45600_GYRO_FS_31_25DPS] = 0,
+	[2 * INV_ICM45600_GYRO_FS_31_25DPS + 1] = 16645,
+	/* +/- 15.625dps => 0.000008322 rad/s */
+	[2 * INV_ICM45600_GYRO_FS_15_625DPS] = 0,
+	[2 * INV_ICM45600_GYRO_FS_15_625DPS + 1] = 8322,
+};
+
+/* IIO format int + nano */
+static const int inv_icm45686_gyro_scale[] = {
+	/* +/- 4000dps => 0.002130529 rad/s */
+	[2 * INV_ICM45686_GYRO_FS_4000DPS] = 0,
+	[2 * INV_ICM45686_GYRO_FS_4000DPS + 1] = 2130529,
+	/* +/- 2000dps => 0.001065264 rad/s */
+	[2 * INV_ICM45686_GYRO_FS_2000DPS] = 0,
+	[2 * INV_ICM45686_GYRO_FS_2000DPS + 1] = 1065264,
+	/* +/- 1000dps => 0.000532632 rad/s */
+	[2 * INV_ICM45686_GYRO_FS_1000DPS] = 0,
+	[2 * INV_ICM45686_GYRO_FS_1000DPS + 1] = 532632,
+	/* +/- 500dps => 0.000266316 rad/s */
+	[2 * INV_ICM45686_GYRO_FS_500DPS] = 0,
+	[2 * INV_ICM45686_GYRO_FS_500DPS + 1] = 266316,
+	/* +/- 250dps => 0.000133158 rad/s */
+	[2 * INV_ICM45686_GYRO_FS_250DPS] = 0,
+	[2 * INV_ICM45686_GYRO_FS_250DPS + 1] = 133158,
+	/* +/- 125dps => 0.000066579 rad/s */
+	[2 * INV_ICM45686_GYRO_FS_125DPS] = 0,
+	[2 * INV_ICM45686_GYRO_FS_125DPS + 1] = 66579,
+	/* +/- 62.5dps => 0.000033290 rad/s */
+	[2 * INV_ICM45686_GYRO_FS_62_5DPS] = 0,
+	[2 * INV_ICM45686_GYRO_FS_62_5DPS + 1] = 33290,
+	/* +/- 31.25dps => 0.000016645 rad/s */
+	[2 * INV_ICM45686_GYRO_FS_31_25DPS] = 0,
+	[2 * INV_ICM45686_GYRO_FS_31_25DPS + 1] = 16645,
+	/* +/- 15.625dps => 0.000008322 rad/s */
+	[2 * INV_ICM45686_GYRO_FS_15_625DPS] = 0,
+	[2 * INV_ICM45686_GYRO_FS_15_625DPS + 1] = 8322,
+};
+
+static int inv_icm45600_gyro_read_scale(struct iio_dev *indio_dev,
+					int *val, int *val2)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
+	unsigned int idx;
+
+	idx = st->conf.gyro.fs;
+
+	/* Full scale register starts at 1 for not High FSR parts */
+	if (gyro_st->scales == inv_icm45600_gyro_scale)
+		idx--;
+
+	*val = gyro_st->scales[2 * idx];
+	*val2 = gyro_st->scales[2 * idx + 1];
+	return IIO_VAL_INT_PLUS_NANO;
+}
+
+static int inv_icm45600_gyro_write_scale(struct iio_dev *indio_dev,
+					 int val, int val2)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
+	struct device *dev = regmap_get_device(st->map);
+	unsigned int idx;
+	struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_INIT;
+	int ret;
+
+	for (idx = 0; idx < gyro_st->scales_len; idx += 2) {
+		if (val == gyro_st->scales[idx] &&
+		    val2 == gyro_st->scales[idx + 1])
+			break;
+	}
+	if (idx >= gyro_st->scales_len)
+		return -EINVAL;
+
+	conf.fs = idx / 2;
+
+	/* Full scale register starts at 1 for not High FSR parts */
+	if (gyro_st->scales == inv_icm45600_gyro_scale)
+		conf.fs++;
+
+	pm_runtime_get_sync(dev);
+	scoped_guard(mutex, &st->lock) {
+		ret = inv_icm45600_set_gyro_conf(st, &conf, NULL);
+	}
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+
+	return ret;
+}
+
+/* IIO format int + micro */
+static const int inv_icm45600_gyro_odr[] = {
+	/* 1.5625Hz */
+	1, 562500,
+	/* 3.125Hz */
+	3, 125000,
+	/* 6.25Hz */
+	6, 250000,
+	/* 12.5Hz */
+	12, 500000,
+	/* 25Hz */
+	25, 0,
+	/* 50Hz */
+	50, 0,
+	/* 100Hz */
+	100, 0,
+	/* 200Hz */
+	200, 0,
+	/* 400Hz */
+	400, 0,
+	/* 800Hz */
+	800, 0,
+	/* 1.6kHz */
+	1600, 0,
+	/* 3.2kHz */
+	3200, 0,
+	/* 6.4kHz */
+	6400, 0,
+};
+
+static const int inv_icm45600_gyro_odr_conv[] = {
+	INV_ICM45600_ODR_1_5625HZ_LP,
+	INV_ICM45600_ODR_3_125HZ_LP,
+	INV_ICM45600_ODR_6_25HZ_LP,
+	INV_ICM45600_ODR_12_5HZ,
+	INV_ICM45600_ODR_25HZ,
+	INV_ICM45600_ODR_50HZ,
+	INV_ICM45600_ODR_100HZ,
+	INV_ICM45600_ODR_200HZ,
+	INV_ICM45600_ODR_400HZ,
+	INV_ICM45600_ODR_800HZ_LN,
+	INV_ICM45600_ODR_1600HZ_LN,
+	INV_ICM45600_ODR_3200HZ_LN,
+	INV_ICM45600_ODR_6400HZ_LN,
+};
+
+static int inv_icm45600_gyro_read_odr(struct inv_icm45600_state *st,
+				      int *val, int *val2)
+{
+	unsigned int odr;
+	unsigned int i;
+
+	odr = st->conf.gyro.odr;
+
+	for (i = 0; i < ARRAY_SIZE(inv_icm45600_gyro_odr_conv); ++i) {
+		if (inv_icm45600_gyro_odr_conv[i] == odr)
+			break;
+	}
+	if (i >= ARRAY_SIZE(inv_icm45600_gyro_odr_conv))
+		return -EINVAL;
+
+	*val = inv_icm45600_gyro_odr[2 * i];
+	*val2 = inv_icm45600_gyro_odr[2 * i + 1];
+
+	return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int inv_icm45600_gyro_write_odr(struct iio_dev *indio_dev,
+				       int val, int val2)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
+	struct inv_sensors_timestamp *ts = &gyro_st->ts;
+	struct device *dev = regmap_get_device(st->map);
+	unsigned int idx;
+	struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_INIT;
+	int ret;
+
+	for (idx = 0; idx < ARRAY_SIZE(inv_icm45600_gyro_odr); idx += 2) {
+		if (val == inv_icm45600_gyro_odr[idx] &&
+		    val2 == inv_icm45600_gyro_odr[idx + 1])
+			break;
+	}
+	if (idx >= ARRAY_SIZE(inv_icm45600_gyro_odr))
+		return -EINVAL;
+
+	conf.odr = inv_icm45600_gyro_odr_conv[idx / 2];
+
+	pm_runtime_get_sync(dev);
+	scoped_guard(mutex, &st->lock) {
+		ret = inv_sensors_timestamp_update_odr(ts, inv_icm45600_odr_to_period(conf.odr),
+						       iio_buffer_enabled(indio_dev));
+		if (ret)
+			break;
+
+		if (st->conf.gyro.mode != INV_ICM45600_SENSOR_MODE_OFF)
+			conf.mode = gyro_st->power_mode;
+		ret = inv_icm45600_set_gyro_conf(st, &conf, NULL);
+		if (ret)
+			break;
+		inv_icm45600_buffer_update_fifo_period(st);
+		inv_icm45600_buffer_update_watermark(st);
+	}
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+
+	return ret;
+}
+
+/*
+ * Calibration bias values, IIO range format int + nano.
+ * Value is limited to +/-64dps coded on 12 bits signed. Step is 1/32 dps.
+ */
+static int inv_icm45600_gyro_calibbias[] = {
+	-1, 117010721,		/* min: -1.117010721 rad/s */
+	0, 545415,		/* step: 0.000545415 rad/s */
+	1, 116465306,		/* max: 1.116465306 rad/s */
+};
+
+static int inv_icm45600_gyro_read_offset(struct inv_icm45600_state *st,
+					 struct iio_chan_spec const *chan,
+					 int *val, int *val2)
+{
+	struct device *dev = regmap_get_device(st->map);
+	int64_t val64;
+	int32_t bias;
+	unsigned int reg;
+	int16_t offset;
+	uint8_t data[2];
+	int ret;
+
+	if (chan->type != IIO_ANGL_VEL)
+		return -EINVAL;
+
+	switch (chan->channel2) {
+	case IIO_MOD_X:
+		reg = INV_ICM45600_IPREG_SYS1_REG_42;
+		break;
+	case IIO_MOD_Y:
+		reg = INV_ICM45600_IPREG_SYS1_REG_56;
+		break;
+	case IIO_MOD_Z:
+		reg = INV_ICM45600_IPREG_SYS1_REG_70;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	pm_runtime_get_sync(dev);
+	scoped_guard(mutex, &st->lock) {
+		ret = regmap_bulk_read(st->map, reg, st->buffer, sizeof(data));
+		memcpy(data, st->buffer, sizeof(data));
+	}
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+	if (ret)
+		return ret;
+
+	/* 12 bits signed value */
+	switch (chan->channel2) {
+	case IIO_MOD_X:
+		offset = sign_extend32(((data[1] & 0x0F) << 8) | data[0], 11);
+		break;
+	case IIO_MOD_Y:
+		offset = sign_extend32(((data[0] & 0xF0) << 4) | data[1], 11);
+		break;
+	case IIO_MOD_Z:
+		offset = sign_extend32(((data[1] & 0x0F) << 8) | data[0], 11);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/*
+	 * convert raw offset to dps then to rad/s
+	 * 12 bits signed raw max 64 to dps: 64 / 2048
+	 * dps to rad: Pi / 180
+	 * result in nano (1000000000)
+	 * (offset * 64 * Pi * 1000000000) / (2048 * 180)
+	 */
+	val64 = (int64_t)offset * 64LL * 3141592653LL;
+	/* for rounding, add + or - divisor (2048 * 180) divided by 2 */
+	if (val64 >= 0)
+		val64 += 2048 * 180 / 2;
+	else
+		val64 -= 2048 * 180 / 2;
+	bias = div_s64(val64, 2048 * 180);
+	*val = bias / 1000000000L;
+	*val2 = bias % 1000000000L;
+
+	return IIO_VAL_INT_PLUS_NANO;
+}
+
+static int inv_icm45600_gyro_write_offset(struct inv_icm45600_state *st,
+					  struct iio_chan_spec const *chan,
+					  int val, int val2)
+{
+	struct device *dev = regmap_get_device(st->map);
+	int64_t val64, min, max;
+	unsigned int reg;
+	int16_t offset;
+	int ret;
+
+	if (chan->type != IIO_ANGL_VEL)
+		return -EINVAL;
+
+	switch (chan->channel2) {
+	case IIO_MOD_X:
+		reg = INV_ICM45600_IPREG_SYS1_REG_42;
+		break;
+	case IIO_MOD_Y:
+		reg = INV_ICM45600_IPREG_SYS1_REG_56;
+		break;
+	case IIO_MOD_Z:
+		reg = INV_ICM45600_IPREG_SYS1_REG_70;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* inv_icm45600_gyro_calibbias: min - step - max in nano */
+	min = (int64_t)inv_icm45600_gyro_calibbias[0] * 1000000000LL +
+	      (int64_t)inv_icm45600_gyro_calibbias[1];
+	max = (int64_t)inv_icm45600_gyro_calibbias[4] * 1000000000LL +
+	      (int64_t)inv_icm45600_gyro_calibbias[5];
+	val64 = (int64_t)val * 1000000000LL + (int64_t)val2;
+	if (val64 < min || val64 > max)
+		return -EINVAL;
+
+	/*
+	 * convert rad/s to dps then to raw value
+	 * rad to dps: 180 / Pi
+	 * dps to raw 14 bits signed, max 62.5: 8192 / 62.5
+	 * val in nano (1000000000)
+	 * val * 180 * 8192 / (Pi * 1000000000 * 62.5)
+	 */
+	val64 = val64 * 180LL * 8192;
+	/* for rounding, add + or - divisor (314159265 * 625) divided by 2 */
+	if (val64 >= 0)
+		val64 += 314159265LL * 625LL / 2LL;
+	else
+		val64 -= 314159265LL * 625LL / 2LL;
+	offset = div64_s64(val64, 314159265LL * 625LL);
+
+	/* clamp value limited to 14 bits signed */
+	if (offset < -8192)
+		offset = -8192;
+	else if (offset > 8191)
+		offset = 8191;
+
+	pm_runtime_get_sync(dev);
+	scoped_guard(mutex, &st->lock) {
+		ret = regmap_bulk_write(st->map, reg, st->buffer, 2);
+	}
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+	return ret;
+}
+
+static int inv_icm45600_gyro_read_raw(struct iio_dev *indio_dev,
+				      struct iio_chan_spec const *chan,
+				      int *val, int *val2, long mask)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	int16_t data;
+	int ret;
+
+	switch (chan->type) {
+	case IIO_ANGL_VEL:
+		break;
+	case IIO_TEMP:
+		return inv_icm45600_temp_read_raw(indio_dev, chan, val, val2, mask);
+	default:
+		return -EINVAL;
+	}
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (!iio_device_claim_direct(indio_dev))
+			return -EBUSY;
+		ret = inv_icm45600_gyro_read_sensor(indio_dev, chan, &data);
+		iio_device_release_direct(indio_dev);
+		if (ret)
+			return ret;
+		*val = data;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		return inv_icm45600_gyro_read_scale(indio_dev, val, val2);
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return inv_icm45600_gyro_read_odr(st, val, val2);
+	case IIO_CHAN_INFO_CALIBBIAS:
+		return inv_icm45600_gyro_read_offset(st, chan, val, val2);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int inv_icm45600_gyro_read_avail(struct iio_dev *indio_dev,
+					struct iio_chan_spec const *chan,
+					const int **vals,
+					int *type, int *length, long mask)
+{
+	struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
+
+	if (chan->type != IIO_ANGL_VEL)
+		return -EINVAL;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		*vals = gyro_st->scales;
+		*type = IIO_VAL_INT_PLUS_NANO;
+		*length = gyro_st->scales_len;
+		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		*vals = inv_icm45600_gyro_odr;
+		*type = IIO_VAL_INT_PLUS_MICRO;
+		*length = ARRAY_SIZE(inv_icm45600_gyro_odr);
+		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_CALIBBIAS:
+		*vals = inv_icm45600_gyro_calibbias;
+		*type = IIO_VAL_INT_PLUS_NANO;
+		return IIO_AVAIL_RANGE;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int inv_icm45600_gyro_write_raw(struct iio_dev *indio_dev,
+				       struct iio_chan_spec const *chan,
+				       int val, int val2, long mask)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	int ret;
+
+	if (chan->type != IIO_ANGL_VEL)
+		return -EINVAL;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		if (!iio_device_claim_direct(indio_dev))
+			return -EBUSY;
+		ret = inv_icm45600_gyro_write_scale(indio_dev, val, val2);
+		iio_device_release_direct(indio_dev);
+		return ret;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return inv_icm45600_gyro_write_odr(indio_dev, val, val2);
+	case IIO_CHAN_INFO_CALIBBIAS:
+		if (!iio_device_claim_direct(indio_dev))
+			return -EBUSY;
+		ret = inv_icm45600_gyro_write_offset(st, chan, val, val2);
+		iio_device_release_direct(indio_dev);
+		return ret;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int inv_icm45600_gyro_write_raw_get_fmt(struct iio_dev *indio_dev,
+					       struct iio_chan_spec const *chan,
+					       long mask)
+{
+	if (chan->type != IIO_ANGL_VEL)
+		return -EINVAL;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		return IIO_VAL_INT_PLUS_NANO;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return IIO_VAL_INT_PLUS_MICRO;
+	case IIO_CHAN_INFO_CALIBBIAS:
+		return IIO_VAL_INT_PLUS_NANO;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int inv_icm45600_gyro_hwfifo_set_watermark(struct iio_dev *indio_dev,
+						  unsigned int val)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	st->fifo.watermark.gyro = val;
+	ret = inv_icm45600_buffer_update_watermark(st);
+
+	return ret;
+}
+
+static int inv_icm45600_gyro_hwfifo_flush(struct iio_dev *indio_dev,
+					  unsigned int count)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	int ret;
+
+	if (count == 0)
+		return 0;
+
+	guard(mutex)(&st->lock);
+
+	ret = inv_icm45600_buffer_hwfifo_flush(st, count);
+	if (!ret)
+		ret = st->fifo.nb.gyro;
+
+	return ret;
+}
+
+static const struct iio_info inv_icm45600_gyro_info = {
+	.read_raw = inv_icm45600_gyro_read_raw,
+	.read_avail = inv_icm45600_gyro_read_avail,
+	.write_raw = inv_icm45600_gyro_write_raw,
+	.write_raw_get_fmt = inv_icm45600_gyro_write_raw_get_fmt,
+	.debugfs_reg_access = inv_icm45600_debugfs_reg,
+	.update_scan_mode = inv_icm45600_gyro_update_scan_mode,
+	.hwfifo_set_watermark = inv_icm45600_gyro_hwfifo_set_watermark,
+	.hwfifo_flush_to_buffer = inv_icm45600_gyro_hwfifo_flush,
+};
+
+struct iio_dev *inv_icm45600_gyro_init(struct inv_icm45600_state *st)
+{
+	struct device *dev = regmap_get_device(st->map);
+	const char *name;
+	struct inv_icm45600_sensor_state *gyro_st;
+	struct inv_sensors_timestamp_chip ts_chip;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	name = devm_kasprintf(dev, GFP_KERNEL, "%s-gyro", st->name);
+	if (!name)
+		return ERR_PTR(-ENOMEM);
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*gyro_st));
+	if (!indio_dev)
+		return ERR_PTR(-ENOMEM);
+	gyro_st = iio_priv(indio_dev);
+
+	switch (st->chip) {
+	case INV_CHIP_ICM45686:
+	case INV_CHIP_ICM45688P:
+	case INV_CHIP_ICM45689:
+	case INV_CHIP_ICM45687:
+		gyro_st->scales = inv_icm45686_gyro_scale;
+		gyro_st->scales_len = ARRAY_SIZE(inv_icm45686_gyro_scale);
+		break;
+	default:
+		gyro_st->scales = inv_icm45600_gyro_scale;
+		gyro_st->scales_len = ARRAY_SIZE(inv_icm45600_gyro_scale);
+		/* Set Gyro default FSR */
+		ret = regmap_update_bits(st->map, INV_ICM45600_REG_GYRO_CONFIG0,
+					INV_ICM45600_GYRO_CONFIG0_FS_MASK,
+					INV_ICM45600_GYRO_CONFIG0_FS_2000DPS);
+		if (ret)
+			return ERR_PTR(ret);
+		break;
+	}
+	/* low-noise by default at init */
+	gyro_st->power_mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE;
+
+	/*
+	 * clock period is 32kHz (31250ns)
+	 * jitter is +/- 2% (20 per mille)
+	 */
+	ts_chip.clock_period = 31250;
+	ts_chip.jitter = 20;
+	ts_chip.init_period = inv_icm45600_odr_to_period(st->conf.gyro.odr);
+	inv_sensors_timestamp_init(&gyro_st->ts, &ts_chip);
+
+	iio_device_set_drvdata(indio_dev, st);
+	indio_dev->name = name;
+	indio_dev->info = &inv_icm45600_gyro_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = inv_icm45600_gyro_channels;
+	indio_dev->num_channels = ARRAY_SIZE(inv_icm45600_gyro_channels);
+	indio_dev->available_scan_masks = inv_icm45600_gyro_scan_masks;
+	indio_dev->setup_ops = &inv_icm45600_buffer_ops;
+
+	ret = devm_iio_kfifo_buffer_setup(dev, indio_dev,
+					  &inv_icm45600_buffer_ops);
+	if (ret)
+		return ERR_PTR(ret);
+
+	ret = devm_iio_device_register(dev, indio_dev);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return indio_dev;
+}
+
+int inv_icm45600_gyro_parse_fifo(struct iio_dev *indio_dev)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
+	struct inv_sensors_timestamp *ts = &gyro_st->ts;
+	ssize_t i, size;
+	unsigned int no;
+	const void *accel, *gyro, *timestamp;
+	const int8_t *temp;
+	unsigned int odr;
+	int64_t ts_val;
+	struct inv_icm45600_gyro_buffer buffer;
+
+	/* parse all fifo packets */
+	for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) {
+		size = inv_icm45600_fifo_decode_packet(&st->fifo.data[i],
+				&accel, &gyro, &temp, &timestamp, &odr);
+		/* quit if error or FIFO is empty */
+		if (size <= 0)
+			return size;
+
+		/* skip packet if no gyro data or data is invalid */
+		if (gyro == NULL || !inv_icm45600_fifo_is_data_valid(gyro))
+			continue;
+
+		/* update odr */
+		if (odr & INV_ICM45600_SENSOR_GYRO)
+			inv_sensors_timestamp_apply_odr(ts, st->fifo.period,
+							st->fifo.nb.total, no);
+
+		/* buffer is copied to userspace, zeroing it to avoid any data leak */
+		memset(&buffer, 0, sizeof(buffer));
+		memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro));
+		/* convert 8 bits FIFO temperature in high resolution format */
+		buffer.temp = temp ? (*temp * 64) : 0;
+		ts_val = inv_sensors_timestamp_pop(ts);
+		iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val);
+	}
+
+	return 0;
+}
diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_temp.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_temp.c
new file mode 100644
index 0000000000000000000000000000000000000000..b2b5697da407de0a0338841eb858b4322996923c
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_temp.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2025 Invensense, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/iio/iio.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include "inv_icm45600_temp.h"
+#include "inv_icm45600.h"
+
+static int inv_icm45600_temp_read(struct inv_icm45600_state *st, int16_t *temp)
+{
+	struct device *dev = regmap_get_device(st->map);
+	__le16 *raw;
+	int ret;
+
+	pm_runtime_get_sync(dev);
+	scoped_guard(mutex, &st->lock) {
+		raw = (__le16 *)&st->buffer[0];
+		ret = regmap_bulk_read(st->map, INV_ICM45600_REG_TEMP_DATA, raw, sizeof(*raw));
+		if (ret)
+			break;
+
+		*temp = (int16_t)le16_to_cpup(raw);
+		if (*temp == INV_ICM45600_DATA_INVALID)
+			ret = -EINVAL;
+	}
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+
+	return ret;
+}
+
+int inv_icm45600_temp_read_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int *val, int *val2, long mask)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	int16_t temp;
+	int ret;
+
+	if (chan->type != IIO_TEMP)
+		return -EINVAL;
+
+	/* temperature sensor work only with accel and/or gyro */
+	if (st->conf.accel.mode <= INV_ICM45600_SENSOR_MODE_STANDBY &&
+		st->conf.gyro.mode  <= INV_ICM45600_SENSOR_MODE_STANDBY) {
+		return -ENODATA;
+	}
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (!iio_device_claim_direct(indio_dev))
+			return -EBUSY;
+		ret = inv_icm45600_temp_read(st, &temp);
+		iio_device_release_direct(indio_dev);
+		if (ret)
+			return ret;
+		*val = temp;
+		return IIO_VAL_INT;
+	/*
+	 * T°C = (temp / 128) + 25
+	 * Tm°C = 1000 * ((temp * 100 / 12800) + 25)
+	 * scale: 100000 / 13248 = 7.8125
+	 * offset: 25000
+	 */
+	case IIO_CHAN_INFO_SCALE:
+		*val = 7;
+		*val2 = 812500;
+		return IIO_VAL_INT_PLUS_MICRO;
+	case IIO_CHAN_INFO_OFFSET:
+		*val = 25000;
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+}
diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_temp.h b/drivers/iio/imu/inv_icm45600/inv_icm45600_temp.h
new file mode 100644
index 0000000000000000000000000000000000000000..1b93e1417e2ec1292e44f05b98c6393354c5297c
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_temp.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2025 Invensense, Inc.
+ */
+
+#ifndef INV_ICM45600_TEMP_H_
+#define INV_ICM45600_TEMP_H_
+
+#include <linux/iio/iio.h>
+
+#define INV_ICM45600_TEMP_CHAN(_index)					\
+	{								\
+		.type = IIO_TEMP,					\
+		.info_mask_separate =					\
+			BIT(IIO_CHAN_INFO_RAW) |			\
+			BIT(IIO_CHAN_INFO_OFFSET) |			\
+			BIT(IIO_CHAN_INFO_SCALE),			\
+		.scan_index = _index,					\
+		.scan_type = {						\
+			.sign = 's',					\
+			.realbits = 16,					\
+			.storagebits = 16,				\
+			.endianness = IIO_LE,				\
+		},							\
+	}
+
+int inv_icm45600_temp_read_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int *val, int *val2, long mask);
+
+#endif

-- 
2.34.1



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

* [PATCH 2/8] iio: imu: inv_icm45600: add I2C driver for inv_icm45600 driver
  2025-04-11 13:28 [PATCH 0/8] iio: imu: new inv_icm45600 driver Remi Buisson via B4 Relay
  2025-04-11 13:28 ` [PATCH 1/8] iio: imu: inv_icm45600: add " Remi Buisson via B4 Relay
@ 2025-04-11 13:28 ` Remi Buisson via B4 Relay
  2025-04-11 19:21   ` Andy Shevchenko
  2025-04-11 13:28 ` [PATCH 3/8] iio: imu: inv_icm45600: add SPI " Remi Buisson via B4 Relay
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 19+ messages in thread
From: Remi Buisson via B4 Relay @ 2025-04-11 13:28 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-kernel, linux-iio, devicetree, Remi Buisson

From: Remi Buisson <remi.buisson@tdk.com>

Add I2C driver for InvenSense ICM-456xxx devices.

Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
---
 drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c | 103 ++++++++++++++++++++++++
 1 file changed, 103 insertions(+)

diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c
new file mode 100644
index 0000000000000000000000000000000000000000..93fa9e7570846b6dfc3cfb96a2d6c058da3cc746
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2025 InvenSense, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include "inv_icm45600.h"
+
+static const struct regmap_config inv_icm45600_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+};
+
+static int inv_icm45600_probe(struct i2c_client *client)
+{
+	const void *match;
+	enum inv_icm45600_chip chip;
+	struct regmap *regmap;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
+		return -ENOTSUPP;
+
+	match = device_get_match_data(&client->dev);
+	if (!match)
+		return -EINVAL;
+	chip = (uintptr_t)match;
+
+	regmap = devm_regmap_init_i2c(client, &inv_icm45600_regmap_config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	return inv_icm45600_core_probe(regmap, chip, true, NULL);
+}
+
+/*
+ * device id table is used to identify what device can be
+ * supported by this driver
+ */
+static const struct i2c_device_id inv_icm45600_id[] = {
+	{ "icm45605", INV_CHIP_ICM45605 },
+	{ "icm45686", INV_CHIP_ICM45686 },
+	{ "icm45688p", INV_CHIP_ICM45688P },
+	{ "icm45608", INV_CHIP_ICM45608 },
+	{ "icm45634", INV_CHIP_ICM45634 },
+	{ "icm45689", INV_CHIP_ICM45689 },
+	{ "icm45606", INV_CHIP_ICM45606 },
+	{ "icm45687", INV_CHIP_ICM45687 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, inv_icm45600_id);
+
+static const struct of_device_id inv_icm45600_of_matches[] = {
+	{
+		.compatible = "invensense,icm45605",
+		.data = (void *)INV_CHIP_ICM45605,
+	}, {
+		.compatible = "invensense,icm45686",
+		.data = (void *)INV_CHIP_ICM45686,
+	}, {
+		.compatible = "invensense,icm45688p",
+		.data = (void *)INV_CHIP_ICM45688P,
+	}, {
+		.compatible = "invensense,icm45608",
+		.data = (void *)INV_CHIP_ICM45608,
+	}, {
+		.compatible = "invensense,icm45634",
+		.data = (void *)INV_CHIP_ICM45634,
+	}, {
+		.compatible = "invensense,icm45689",
+		.data = (void *)INV_CHIP_ICM45689,
+	}, {
+		.compatible = "invensense,icm45606",
+		.data = (void *)INV_CHIP_ICM45606,
+	}, {
+		.compatible = "invensense,icm45687",
+		.data = (void *)INV_CHIP_ICM45687,
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, inv_icm45600_of_matches);
+
+static struct i2c_driver inv_icm45600_driver = {
+	.driver = {
+		.name = "inv-icm45600-i2c",
+		.of_match_table = inv_icm45600_of_matches,
+		.pm = pm_ptr(&inv_icm45600_pm_ops),
+	},
+	.id_table = inv_icm45600_id,
+	.probe = inv_icm45600_probe,
+};
+module_i2c_driver(inv_icm45600_driver);
+
+MODULE_AUTHOR("InvenSense, Inc.");
+MODULE_DESCRIPTION("InvenSense ICM-456xx I2C driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_ICM45600");

-- 
2.34.1



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

* [PATCH 3/8] iio: imu: inv_icm45600: add SPI driver for inv_icm45600 driver
  2025-04-11 13:28 [PATCH 0/8] iio: imu: new inv_icm45600 driver Remi Buisson via B4 Relay
  2025-04-11 13:28 ` [PATCH 1/8] iio: imu: inv_icm45600: add " Remi Buisson via B4 Relay
  2025-04-11 13:28 ` [PATCH 2/8] iio: imu: inv_icm45600: add I2C driver for " Remi Buisson via B4 Relay
@ 2025-04-11 13:28 ` Remi Buisson via B4 Relay
  2025-04-11 13:28 ` [PATCH 4/8] iio: imu: inv_icm45600: add I3C " Remi Buisson via B4 Relay
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 19+ messages in thread
From: Remi Buisson via B4 Relay @ 2025-04-11 13:28 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-kernel, linux-iio, devicetree, Remi Buisson

From: Remi Buisson <remi.buisson@tdk.com>

Add SPI driver for InvenSense ICM-456xxx devices.

Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
---
 drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c | 110 ++++++++++++++++++++++++
 1 file changed, 110 insertions(+)

diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c
new file mode 100644
index 0000000000000000000000000000000000000000..fba0953343aaf9a6009a740d3a6e6446b5bea53f
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2025 InvenSense, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "inv_icm45600.h"
+
+static const struct regmap_config inv_icm45600_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+};
+
+static int inv_icm45600_spi_bus_setup(struct inv_icm45600_state *st)
+{
+	/* set slew rates for SPI */
+	return regmap_update_bits(st->map, INV_ICM45600_REG_DRIVE_CONFIG0,
+				INV_ICM45600_DRIVE_CONFIG0_SPI_MASK,
+				INV_ICM45600_SPI_SLEW_RATE_5NS);
+}
+
+static int inv_icm45600_probe(struct spi_device *spi)
+{
+	const void *match;
+	enum inv_icm45600_chip chip;
+	struct regmap *regmap;
+
+	match = device_get_match_data(&spi->dev);
+	if (!match)
+		return -EINVAL;
+	chip = (uintptr_t)match;
+
+	/* use SPI specific regmap */
+	regmap = devm_regmap_init_spi(spi, &inv_icm45600_regmap_config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	return inv_icm45600_core_probe(regmap, chip, true,
+				       inv_icm45600_spi_bus_setup);
+}
+
+/*
+ * device id table is used to identify what device can be
+ * supported by this driver
+ */
+static const struct spi_device_id inv_icm45600_id[] = {
+	{ "icm45605", INV_CHIP_ICM45605 },
+	{ "icm45686", INV_CHIP_ICM45686 },
+	{ "icm45688p", INV_CHIP_ICM45688P },
+	{ "icm45608", INV_CHIP_ICM45608 },
+	{ "icm45634", INV_CHIP_ICM45634 },
+	{ "icm45689", INV_CHIP_ICM45689 },
+	{ "icm45606", INV_CHIP_ICM45606 },
+	{ "icm45687", INV_CHIP_ICM45687 },
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, inv_icm45600_id);
+
+static const struct of_device_id inv_icm45600_of_matches[] = {
+	{
+		.compatible = "invensense,icm45605",
+		.data = (void *)INV_CHIP_ICM45605,
+	}, {
+		.compatible = "invensense,icm45686",
+		.data = (void *)INV_CHIP_ICM45686,
+	}, {
+		.compatible = "invensense,icm45688p",
+		.data = (void *)INV_CHIP_ICM45688P,
+	}, {
+		.compatible = "invensense,icm45608",
+		.data = (void *)INV_CHIP_ICM45608,
+	}, {
+		.compatible = "invensense,icm45634",
+		.data = (void *)INV_CHIP_ICM45634,
+	}, {
+		.compatible = "invensense,icm45689",
+		.data = (void *)INV_CHIP_ICM45689,
+	}, {
+		.compatible = "invensense,icm45606",
+		.data = (void *)INV_CHIP_ICM45606,
+	}, {
+		.compatible = "invensense,icm45687",
+		.data = (void *)INV_CHIP_ICM45687,
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, inv_icm45600_of_matches);
+
+static struct spi_driver inv_icm45600_driver = {
+	.driver = {
+		.name = "inv-icm45600-spi",
+		.of_match_table = inv_icm45600_of_matches,
+		.pm = pm_ptr(&inv_icm45600_pm_ops),
+	},
+	.id_table = inv_icm45600_id,
+	.probe = inv_icm45600_probe,
+};
+module_spi_driver(inv_icm45600_driver);
+
+MODULE_AUTHOR("InvenSense, Inc.");
+MODULE_DESCRIPTION("InvenSense ICM-456xx SPI driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_ICM45600");

-- 
2.34.1



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

* [PATCH 4/8] iio: imu: inv_icm45600: add I3C driver for inv_icm45600 driver
  2025-04-11 13:28 [PATCH 0/8] iio: imu: new inv_icm45600 driver Remi Buisson via B4 Relay
                   ` (2 preceding siblings ...)
  2025-04-11 13:28 ` [PATCH 3/8] iio: imu: inv_icm45600: add SPI " Remi Buisson via B4 Relay
@ 2025-04-11 13:28 ` Remi Buisson via B4 Relay
  2025-04-11 13:28 ` [PATCH 5/8] iio: imu: inv_icm45600: add buffer support in iio devices Remi Buisson via B4 Relay
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 19+ messages in thread
From: Remi Buisson via B4 Relay @ 2025-04-11 13:28 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-kernel, linux-iio, devicetree, Remi Buisson

From: Remi Buisson <remi.buisson@tdk.com>

Add I3C driver for InvenSense ICM-456xxx devices.

Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
---
 drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c | 84 +++++++++++++++++++++++++
 1 file changed, 84 insertions(+)

diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c
new file mode 100644
index 0000000000000000000000000000000000000000..da939a5da5da88cbbb4a41edd90deae950cb9760
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2025 InvenSense, Inc.
+ */
+
+#include <linux/i3c/device.h>
+#include <linux/i3c/master.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include "inv_icm45600.h"
+
+static const struct regmap_config inv_icm45600_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+};
+
+static const struct i3c_device_id inv_icm45600_i3c_ids[] = {
+	I3C_DEVICE_EXTRA_INFO(0x0235, 0x0000, 0x0011, (void *)NULL),
+	I3C_DEVICE_EXTRA_INFO(0x0235, 0x0000, 0x0084, (void *)NULL),
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(i3c, inv_icm45600_i3c_ids);
+
+static const uint8_t inv_icm45600_i3c_code[INV_CHIP_NB] = {
+	[INV_CHIP_ICM45605] = INV_ICM45600_WHOAMI_ICM45605,
+	[INV_CHIP_ICM45686] = INV_ICM45600_WHOAMI_ICM45686,
+	[INV_CHIP_ICM45688P] = INV_ICM45600_WHOAMI_ICM45688P,
+	[INV_CHIP_ICM45608] = INV_ICM45600_WHOAMI_ICM45608,
+	[INV_CHIP_ICM45634] = INV_ICM45600_WHOAMI_ICM45634,
+	[INV_CHIP_ICM45689] = INV_ICM45600_WHOAMI_ICM45689,
+	[INV_CHIP_ICM45606] = INV_ICM45600_WHOAMI_ICM45606,
+	[INV_CHIP_ICM45687] = INV_ICM45600_WHOAMI_ICM45687
+};
+
+static int inv_icm45600_i3c_probe(struct i3c_device *i3cdev)
+{
+	int ret;
+	unsigned int whoami;
+	enum inv_icm45600_chip chip;
+	struct regmap *regmap;
+
+	regmap = devm_regmap_init_i3c(i3cdev, &inv_icm45600_regmap_config);
+	if (IS_ERR(regmap)) {
+		dev_err(&i3cdev->dev, "Failed to register i3c regmap %ld\n", PTR_ERR(regmap));
+		return PTR_ERR(regmap);
+	}
+
+	ret = regmap_read(regmap, INV_ICM45600_REG_WHOAMI, &whoami);
+	if (ret) {
+		dev_err(&i3cdev->dev, "Failed to read part id %d\n", whoami);
+		return ret;
+	}
+
+	for (chip = INV_CHIP_ICM45605; chip < INV_CHIP_NB; chip++) {
+		if (whoami == inv_icm45600_i3c_code[chip])
+			break;
+	}
+
+	if (chip == INV_CHIP_NB) {
+		dev_err(&i3cdev->dev, "Failed to match part id %d\n", whoami);
+		return -ENODEV;
+	}
+
+	return inv_icm45600_core_probe(regmap, chip, false, NULL);
+}
+
+static struct i3c_driver inv_icm45600_driver = {
+	.driver = {
+		.name = "inv_icm45600_i3c",
+		.pm = pm_sleep_ptr(&inv_icm45600_pm_ops),
+	},
+	.probe = inv_icm45600_i3c_probe,
+	.id_table = inv_icm45600_i3c_ids,
+};
+module_i3c_driver(inv_icm45600_driver);
+
+MODULE_AUTHOR("Remi Buisson <remi.buisson@tdk.com>");
+MODULE_DESCRIPTION("InvenSense ICM-456xx i3c driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_ICM45600");

-- 
2.34.1



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

* [PATCH 5/8] iio: imu: inv_icm45600: add buffer support in iio devices
  2025-04-11 13:28 [PATCH 0/8] iio: imu: new inv_icm45600 driver Remi Buisson via B4 Relay
                   ` (3 preceding siblings ...)
  2025-04-11 13:28 ` [PATCH 4/8] iio: imu: inv_icm45600: add I3C " Remi Buisson via B4 Relay
@ 2025-04-11 13:28 ` Remi Buisson via B4 Relay
  2025-04-12 19:13   ` Jonathan Cameron
  2025-04-11 13:28 ` [PATCH 6/8] iio: imu: add Kconfig and Makefile for inv_icm45600 driver Remi Buisson via B4 Relay
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 19+ messages in thread
From: Remi Buisson via B4 Relay @ 2025-04-11 13:28 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-kernel, linux-iio, devicetree, Remi Buisson

From: Remi Buisson <remi.buisson@tdk.com>

Add all FIFO parsing and reading functions. Add accel and gyro
kfifo buffer and FIFO data parsing. Use device interrupt for
reading data FIFO and launching accel and gyro parsing.

Support hwfifo watermark by multiplexing gyro and accel settings.
Support hwfifo flush.

Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
---
 drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c | 572 +++++++++++++++++++++
 drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h | 100 ++++
 2 files changed, 672 insertions(+)

diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c
new file mode 100644
index 0000000000000000000000000000000000000000..ed6f46eb4865fa3299b0240cc5f520e0326a7648
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c
@@ -0,0 +1,572 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2025 Invensense, Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/common/inv_sensors_timestamp.h>
+#include <linux/iio/iio.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include "inv_icm45600_buffer.h"
+#include "inv_icm45600.h"
+
+/* FIFO header: 1 byte */
+#define INV_ICM45600_FIFO_EXT_HEADER		BIT(7)
+#define INV_ICM45600_FIFO_HEADER_ACCEL		BIT(6)
+#define INV_ICM45600_FIFO_HEADER_GYRO		BIT(5)
+#define INV_ICM45600_FIFO_HEADER_HIGH_RES	BIT(4)
+#define INV_ICM45600_FIFO_HEADER_TMST_FSYNC	GENMASK(3, 2)
+#define INV_ICM45600_FIFO_HEADER_ODR_ACCEL	BIT(1)
+#define INV_ICM45600_FIFO_HEADER_ODR_GYRO	BIT(0)
+
+struct inv_icm45600_fifo_1sensor_packet {
+	uint8_t header;
+	struct inv_icm45600_fifo_sensor_data data;
+	int8_t temp;
+} __packed;
+#define INV_ICM45600_FIFO_1SENSOR_PACKET_SIZE		8
+
+struct inv_icm45600_fifo_2sensors_packet {
+	uint8_t header;
+	struct inv_icm45600_fifo_sensor_data accel;
+	struct inv_icm45600_fifo_sensor_data gyro;
+	int8_t temp;
+	__le16 timestamp;
+} __packed;
+#define INV_ICM45600_FIFO_2SENSORS_PACKET_SIZE		16
+
+ssize_t inv_icm45600_fifo_decode_packet(const void *packet, const void **accel,
+					const void **gyro, const int8_t **temp,
+					const void **timestamp, unsigned int *odr)
+{
+	const struct inv_icm45600_fifo_1sensor_packet *pack1 = packet;
+	const struct inv_icm45600_fifo_2sensors_packet *pack2 = packet;
+	uint8_t header = *((const uint8_t *)packet);
+
+	/* FIFO extended header */
+	if (header & INV_ICM45600_FIFO_EXT_HEADER) {
+		/* Not yet supported */
+		return 0;
+	}
+
+	/* handle odr flags */
+	*odr = 0;
+	if (header & INV_ICM45600_FIFO_HEADER_ODR_GYRO)
+		*odr |= INV_ICM45600_SENSOR_GYRO;
+	if (header & INV_ICM45600_FIFO_HEADER_ODR_ACCEL)
+		*odr |= INV_ICM45600_SENSOR_ACCEL;
+
+	/* accel + gyro */
+	if ((header & INV_ICM45600_FIFO_HEADER_ACCEL) &&
+	    (header & INV_ICM45600_FIFO_HEADER_GYRO)) {
+		*accel = &pack2->accel;
+		*gyro = &pack2->gyro;
+		*temp = &pack2->temp;
+		*timestamp = &pack2->timestamp;
+		return INV_ICM45600_FIFO_2SENSORS_PACKET_SIZE;
+	}
+
+	/* accel only */
+	if (header & INV_ICM45600_FIFO_HEADER_ACCEL) {
+		*accel = &pack1->data;
+		*gyro = NULL;
+		*temp = &pack1->temp;
+		*timestamp = NULL;
+		return INV_ICM45600_FIFO_1SENSOR_PACKET_SIZE;
+	}
+
+	/* gyro only */
+	if (header & INV_ICM45600_FIFO_HEADER_GYRO) {
+		*accel = NULL;
+		*gyro = &pack1->data;
+		*temp = &pack1->temp;
+		*timestamp = NULL;
+		return INV_ICM45600_FIFO_1SENSOR_PACKET_SIZE;
+	}
+
+	/* invalid packet if here */
+	return -EINVAL;
+}
+
+void inv_icm45600_buffer_update_fifo_period(struct inv_icm45600_state *st)
+{
+	uint32_t period_gyro, period_accel, period;
+
+	if (st->fifo.en & INV_ICM45600_SENSOR_GYRO)
+		period_gyro = inv_icm45600_odr_to_period(st->conf.gyro.odr);
+	else
+		period_gyro = U32_MAX;
+
+	if (st->fifo.en & INV_ICM45600_SENSOR_ACCEL)
+		period_accel = inv_icm45600_odr_to_period(st->conf.accel.odr);
+	else
+		period_accel = U32_MAX;
+
+	if (period_gyro <= period_accel)
+		period = period_gyro;
+	else
+		period = period_accel;
+
+	st->fifo.period = period;
+}
+
+int inv_icm45600_buffer_set_fifo_en(struct inv_icm45600_state *st,
+				    unsigned int fifo_en)
+{
+	unsigned int mask, val;
+	int ret;
+
+	/* update only FIFO EN bits */
+	mask = INV_ICM45600_FIFO_CONFIG3_GYRO_EN |
+	       INV_ICM45600_FIFO_CONFIG3_ACCEL_EN;
+
+	val = 0;
+	if ((fifo_en & INV_ICM45600_SENSOR_GYRO) || (fifo_en & INV_ICM45600_SENSOR_ACCEL))
+		val = (INV_ICM45600_FIFO_CONFIG3_GYRO_EN | INV_ICM45600_FIFO_CONFIG3_ACCEL_EN);
+
+	ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3, mask, val);
+	if (ret)
+		return ret;
+
+	st->fifo.en = fifo_en;
+	inv_icm45600_buffer_update_fifo_period(st);
+
+	return 0;
+}
+
+static unsigned int inv_icm45600_wm_truncate(unsigned int watermark, size_t packet_size,
+					     unsigned int fifo_period)
+{
+	size_t watermark_max, grace_samples;
+
+	/* keep 20ms for processing FIFO */
+	grace_samples = (20U * 1000000U) / fifo_period;
+	if (grace_samples < 1)
+		grace_samples = 1;
+
+	watermark_max = INV_ICM45600_FIFO_SIZE_MAX / packet_size;
+	watermark_max -= grace_samples;
+
+	if (watermark > watermark_max)
+		watermark = watermark_max;
+
+	return watermark;
+}
+
+/**
+ * inv_icm45600_buffer_update_watermark - update watermark FIFO threshold
+ * @st:	driver internal state
+ *
+ * Returns 0 on success, a negative error code otherwise.
+ *
+ * FIFO watermark threshold is computed based on the required watermark values
+ * set for gyro and accel sensors. Since watermark is all about acceptable data
+ * latency, use the smallest setting between the 2. It means choosing the
+ * smallest latency but this is not as simple as choosing the smallest watermark
+ * value. Latency depends on watermark and ODR. It requires several steps:
+ * 1) compute gyro and accel latencies and choose the smallest value.
+ * 2) adapt the chosen latency so that it is a multiple of both gyro and accel
+ *    ones. Otherwise it is possible that you don't meet a requirement. (for
+ *    example with gyro @100Hz wm 4 and accel @100Hz with wm 6, choosing the
+ *    value of 4 will not meet accel latency requirement because 6 is not a
+ *    multiple of 4. You need to use the value 2.)
+ * 3) Since all periods are multiple of each others, watermark is computed by
+ *    dividing this computed latency by the smallest period, which corresponds
+ *    to the FIFO frequency.
+ */
+int inv_icm45600_buffer_update_watermark(struct inv_icm45600_state *st)
+{
+	const size_t packet_size = INV_ICM45600_FIFO_2SENSORS_PACKET_SIZE;
+	unsigned int wm_gyro, wm_accel, watermark;
+	uint32_t period_gyro, period_accel, period;
+	uint32_t latency_gyro, latency_accel, latency;
+	__le16 raw_wm;
+	int ret;
+
+	/* compute sensors latency, depending on sensor watermark and odr */
+	wm_gyro = inv_icm45600_wm_truncate(st->fifo.watermark.gyro, packet_size,
+					   st->fifo.period);
+	wm_accel = inv_icm45600_wm_truncate(st->fifo.watermark.accel, packet_size,
+					    st->fifo.period);
+	/* use us for odr to avoid overflow using 32 bits values */
+	period_gyro = inv_icm45600_odr_to_period(st->conf.gyro.odr) / 1000UL;
+	period_accel = inv_icm45600_odr_to_period(st->conf.accel.odr) / 1000UL;
+	latency_gyro = period_gyro * wm_gyro;
+	latency_accel = period_accel * wm_accel;
+
+	/* 0 value for watermark means that the sensor is turned off */
+	if (wm_gyro == 0 && wm_accel == 0)
+		return 0;
+
+	if (latency_gyro == 0) {
+		watermark = wm_accel;
+		st->fifo.watermark.eff_accel = wm_accel;
+	} else if (latency_accel == 0) {
+		watermark = wm_gyro;
+		st->fifo.watermark.eff_gyro = wm_gyro;
+	} else {
+		/* compute the smallest latency that is a multiple of both */
+		if (latency_gyro <= latency_accel)
+			latency = latency_gyro - (latency_accel % latency_gyro);
+		else
+			latency = latency_accel - (latency_gyro % latency_accel);
+		/* use the shortest period */
+		if (period_gyro <= period_accel)
+			period = period_gyro;
+		else
+			period = period_accel;
+		/* all this works because periods are multiple of each others */
+		watermark = latency / period;
+		if (watermark < 1)
+			watermark = 1;
+		/* update effective watermark */
+		st->fifo.watermark.eff_gyro = latency / period_gyro;
+		if (st->fifo.watermark.eff_gyro < 1)
+			st->fifo.watermark.eff_gyro = 1;
+		st->fifo.watermark.eff_accel = latency / period_accel;
+		if (st->fifo.watermark.eff_accel < 1)
+			st->fifo.watermark.eff_accel = 1;
+	}
+
+	raw_wm = INV_ICM45600_FIFO_WATERMARK_VAL(watermark);
+	memcpy(st->buffer, &raw_wm, sizeof(raw_wm));
+	ret = regmap_bulk_write(st->map, INV_ICM45600_REG_FIFO_WATERMARK,
+				st->buffer, sizeof(raw_wm));
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int inv_icm45600_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct device *dev = regmap_get_device(st->map);
+	struct inv_icm45600_sensor_state *sensor_st = iio_priv(indio_dev);
+	struct inv_sensors_timestamp *ts = &sensor_st->ts;
+
+	pm_runtime_get_sync(dev);
+
+	guard(mutex)(&st->lock);
+	inv_sensors_timestamp_reset(ts);
+
+	return 0;
+}
+
+/*
+ * update_scan_mode callback is turning sensors on and setting data FIFO enable
+ * bits.
+ */
+static int inv_icm45600_buffer_postenable(struct iio_dev *indio_dev)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	/* exit if FIFO is already on */
+	if (st->fifo.on) {
+		ret = 0;
+		goto out_on;
+	}
+
+	/* flush FIFO data */
+	ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG2,
+			   INV_ICM45600_REG_FIFO_CONFIG2_FIFO_FLUSH);
+	if (ret)
+		return ret;
+
+	/* set FIFO threshold and full interrupt */
+	ret = regmap_set_bits(st->map, INV_ICM45600_REG_INT1_CONFIG0,
+			      INV_ICM45600_INT1_CONFIG0_FIFO_THS_EN |
+			      INV_ICM45600_INT1_CONFIG0_FIFO_FULL_EN);
+	if (ret)
+		return ret;
+
+	/* set FIFO in streaming mode */
+	ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0,
+				 INV_ICM45600_FIFO_CONFIG0_MODE_MASK,
+				 INV_ICM45600_FIFO_CONFIG0_MODE_STREAM);
+	if (ret)
+		return ret;
+
+	/* enable writing sensor data to FIFO */
+	ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3,
+			      INV_ICM45600_FIFO_CONFIG3_IF_EN);
+	if (ret)
+		return ret;
+
+out_on:
+	/* increase FIFO on counter */
+	st->fifo.on++;
+	return ret;
+}
+
+static int inv_icm45600_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	/* exit if there are several sensors using the FIFO */
+	if (st->fifo.on > 1) {
+		ret = 0;
+		goto out_off;
+	}
+
+	/* disable writing sensor data to FIFO */
+	ret = regmap_clear_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3,
+				INV_ICM45600_FIFO_CONFIG3_IF_EN);
+	if (ret)
+		return ret;
+
+	/* set FIFO in bypass mode */
+	ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0,
+				 INV_ICM45600_FIFO_CONFIG0_MODE_MASK,
+				 INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS);
+	if (ret)
+		return ret;
+
+	/* disable FIFO threshold and full interrupt */
+	ret = regmap_clear_bits(st->map, INV_ICM45600_REG_INT1_CONFIG0,
+				INV_ICM45600_INT1_CONFIG0_FIFO_THS_EN |
+				INV_ICM45600_INT1_CONFIG0_FIFO_FULL_EN);
+	if (ret)
+		return ret;
+
+	/* flush FIFO data */
+	ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG2,
+			   INV_ICM45600_REG_FIFO_CONFIG2_FIFO_FLUSH);
+	if (ret)
+		return ret;
+
+out_off:
+	/* decrease FIFO on counter */
+	st->fifo.on--;
+	return ret;
+}
+
+static int inv_icm45600_buffer_postdisable(struct iio_dev *indio_dev)
+{
+	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
+	struct device *dev = regmap_get_device(st->map);
+	unsigned int sensor;
+	unsigned int *watermark;
+	struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_INIT;
+	unsigned int sleep_temp = 0;
+	unsigned int sleep_sensor = 0;
+	unsigned int sleep;
+	int ret;
+
+	if (indio_dev == st->indio_gyro) {
+		sensor = INV_ICM45600_SENSOR_GYRO;
+		watermark = &st->fifo.watermark.gyro;
+	} else if (indio_dev == st->indio_accel) {
+		sensor = INV_ICM45600_SENSOR_ACCEL;
+		watermark = &st->fifo.watermark.accel;
+	} else {
+		return -EINVAL;
+	}
+
+	scoped_guard(mutex, &st->lock) {
+		ret = inv_icm45600_buffer_set_fifo_en(st, st->fifo.en & ~sensor);
+		if (ret)
+			break;
+
+		*watermark = 0;
+		ret = inv_icm45600_buffer_update_watermark(st);
+		if (ret)
+			break;
+
+		conf.mode = INV_ICM45600_SENSOR_MODE_OFF;
+		if (sensor == INV_ICM45600_SENSOR_GYRO)
+			ret = inv_icm45600_set_gyro_conf(st, &conf, &sleep_sensor);
+		else
+			ret = inv_icm45600_set_accel_conf(st, &conf, &sleep_sensor);
+	}
+	/* sleep maximum required time */
+	sleep = max(sleep_sensor, sleep_temp);
+	if (sleep)
+		msleep(sleep);
+
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+
+	return ret;
+}
+
+const struct iio_buffer_setup_ops inv_icm45600_buffer_ops = {
+	.preenable = inv_icm45600_buffer_preenable,
+	.postenable = inv_icm45600_buffer_postenable,
+	.predisable = inv_icm45600_buffer_predisable,
+	.postdisable = inv_icm45600_buffer_postdisable,
+};
+
+int inv_icm45600_buffer_fifo_read(struct inv_icm45600_state *st,
+				  unsigned int max)
+{
+	const ssize_t packet_size = INV_ICM45600_FIFO_2SENSORS_PACKET_SIZE;
+	__le16 *raw_fifo_count;
+	size_t fifo_nb, i;
+	ssize_t size;
+	const void *accel, *gyro, *timestamp;
+	const int8_t *temp;
+	unsigned int odr;
+	int ret;
+
+	/* reset all samples counters */
+	st->fifo.count = 0;
+	st->fifo.nb.gyro = 0;
+	st->fifo.nb.accel = 0;
+	st->fifo.nb.total = 0;
+
+	/* read FIFO count value */
+	raw_fifo_count = (__le16 *)st->buffer;
+	ret = regmap_bulk_read(st->map, INV_ICM45600_REG_FIFO_COUNT,
+			       raw_fifo_count, sizeof(*raw_fifo_count));
+	if (ret)
+		return ret;
+	fifo_nb = le16_to_cpup(raw_fifo_count);
+
+	/* check and limit number of samples if requested */
+	if (fifo_nb == 0)
+		return 0;
+	if (max > 0 && fifo_nb > max)
+		fifo_nb = max;
+
+	/* Try to read all FIFO data in internal buffer */
+	st->fifo.count = fifo_nb * packet_size;
+	ret = regmap_noinc_read(st->map, INV_ICM45600_REG_FIFO_DATA,
+				st->fifo.data, st->fifo.count);
+	if (ret == -ENOTSUPP || ret == -EFBIG) {
+		/* Read full fifo is not supported, read samples one by one */
+		ret = 0;
+		for (i = 0; i < st->fifo.count && ret == 0; i += packet_size)
+			ret = regmap_noinc_read(st->map, INV_ICM45600_REG_FIFO_DATA,
+							&st->fifo.data[i], packet_size);
+	}
+	if (ret)
+		return ret;
+
+	for (i = 0; i < st->fifo.count; i += size) {
+		size = inv_icm45600_fifo_decode_packet(&st->fifo.data[i],
+			&accel, &gyro, &temp, &timestamp, &odr);
+		if (size <= 0)
+			break;
+		if (gyro != NULL && inv_icm45600_fifo_is_data_valid(gyro))
+			st->fifo.nb.gyro++;
+		if (accel != NULL && inv_icm45600_fifo_is_data_valid(accel))
+			st->fifo.nb.accel++;
+		st->fifo.nb.total++;
+	}
+
+	return 0;
+}
+
+int inv_icm45600_buffer_fifo_parse(struct inv_icm45600_state *st)
+{
+	struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro);
+	struct inv_icm45600_sensor_state *accel_st = iio_priv(st->indio_accel);
+	struct inv_sensors_timestamp *ts;
+	int ret;
+
+	if (st->fifo.nb.total == 0)
+		return 0;
+
+	/* handle gyroscope timestamp and FIFO data parsing */
+	if (st->fifo.nb.gyro > 0) {
+		ts = &gyro_st->ts;
+		inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_gyro,
+						st->timestamp.gyro);
+		ret = inv_icm45600_gyro_parse_fifo(st->indio_gyro);
+		if (ret)
+			return ret;
+	}
+
+	/* handle accelerometer timestamp and FIFO data parsing */
+	if (st->fifo.nb.accel > 0) {
+		ts = &accel_st->ts;
+		inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_accel,
+						st->timestamp.accel);
+		ret = inv_icm45600_accel_parse_fifo(st->indio_accel);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+int inv_icm45600_buffer_hwfifo_flush(struct inv_icm45600_state *st,
+				     unsigned int count)
+{
+	struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro);
+	struct inv_icm45600_sensor_state *accel_st = iio_priv(st->indio_accel);
+	struct inv_sensors_timestamp *ts;
+	int64_t gyro_ts, accel_ts;
+	int ret;
+
+	gyro_ts = iio_get_time_ns(st->indio_gyro);
+	accel_ts = iio_get_time_ns(st->indio_accel);
+
+	ret = inv_icm45600_buffer_fifo_read(st, count);
+	if (ret)
+		return ret;
+
+	if (st->fifo.nb.total == 0)
+		return 0;
+
+	if (st->fifo.nb.gyro > 0) {
+		ts = &gyro_st->ts;
+		inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, gyro_ts);
+		ret = inv_icm45600_gyro_parse_fifo(st->indio_gyro);
+		if (ret)
+			return ret;
+	}
+
+	if (st->fifo.nb.accel > 0) {
+		ts = &accel_st->ts;
+		inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, accel_ts);
+		ret = inv_icm45600_accel_parse_fifo(st->indio_accel);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+int inv_icm45600_buffer_init(struct inv_icm45600_state *st)
+{
+	int ret;
+
+	st->fifo.watermark.eff_gyro = 1;
+	st->fifo.watermark.eff_accel = 1;
+
+	/* Disable all FIFO EN bits. */
+	ret = regmap_write(st->map, INV_ICM45600_REG_FIFO_CONFIG3, 0);
+	if (ret)
+		return ret;
+
+	/* Disable FIFO and set depth */
+	ret = regmap_write(st->map, INV_ICM45600_REG_FIFO_CONFIG0,
+			   INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS |
+			   INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MAX);
+	if (ret)
+		return ret;
+
+	/* enable only timestamp in fifo, disable compression */
+	ret = regmap_write(st->map, INV_ICM45600_REG_FIFO_CONFIG4,
+			   INV_ICM45600_FIFO_CONFIG4_TMST_FSYNC_EN);
+	if (ret)
+		return ret;
+
+	/* Enable FIFO continuous watermark interrupt. */
+	return regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG2,
+			       INV_ICM45600_REG_FIFO_CONFIG2_WM_GT_TH);
+}
diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h
new file mode 100644
index 0000000000000000000000000000000000000000..f725f841e07bc1e738ca7df07accd50484c8ceda
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2025 Invensense, Inc.
+ */
+
+#ifndef INV_ICM45600_BUFFER_H_
+#define INV_ICM45600_BUFFER_H_
+
+#include <linux/bits.h>
+#include <linux/kernel.h>
+
+struct inv_icm45600_state;
+
+#define INV_ICM45600_SENSOR_GYRO	BIT(0)
+#define INV_ICM45600_SENSOR_ACCEL	BIT(1)
+#define INV_ICM45600_SENSOR_TEMP	BIT(2)
+
+/**
+ * struct inv_icm45600_fifo - FIFO state variables
+ * @on:		reference counter for FIFO on.
+ * @en:		bits field of INV_ICM45600_SENSOR_* for FIFO EN bits.
+ * @period:	FIFO internal period.
+ * @watermark:	watermark configuration values for accel and gyro.
+ * @count:	number of bytes in the FIFO data buffer.
+ * @nb:		gyro, accel and total samples in the FIFO data buffer.
+ * @data:	FIFO data buffer aligned for DMA (8kB)
+ */
+struct inv_icm45600_fifo {
+	unsigned int on;
+	unsigned int en;
+	uint32_t period;
+	struct {
+		unsigned int gyro;
+		unsigned int accel;
+		unsigned int eff_gyro;
+		unsigned int eff_accel;
+	} watermark;
+	size_t count;
+	struct {
+		size_t gyro;
+		size_t accel;
+		size_t total;
+	} nb;
+	uint8_t data[8192] __aligned(IIO_DMA_MINALIGN);
+};
+
+/* FIFO data packet */
+struct inv_icm45600_fifo_sensor_data {
+	__le16 x;
+	__le16 y;
+	__le16 z;
+} __packed;
+#define INV_ICM45600_FIFO_DATA_INVALID		-32768
+
+static inline int16_t inv_icm45600_fifo_get_sensor_data(__le16 d)
+{
+	return le16_to_cpu(d);
+}
+
+static inline bool
+inv_icm45600_fifo_is_data_valid(const struct inv_icm45600_fifo_sensor_data *s)
+{
+	int16_t x, y, z;
+
+	x = inv_icm45600_fifo_get_sensor_data(s->x);
+	y = inv_icm45600_fifo_get_sensor_data(s->y);
+	z = inv_icm45600_fifo_get_sensor_data(s->z);
+
+	if (x == INV_ICM45600_FIFO_DATA_INVALID &&
+	    y == INV_ICM45600_FIFO_DATA_INVALID &&
+	    z == INV_ICM45600_FIFO_DATA_INVALID)
+		return false;
+
+	return true;
+}
+
+ssize_t inv_icm45600_fifo_decode_packet(const void *packet, const void **accel,
+					const void **gyro, const int8_t **temp,
+					const void **timestamp, unsigned int *odr);
+
+extern const struct iio_buffer_setup_ops inv_icm45600_buffer_ops;
+
+int inv_icm45600_buffer_init(struct inv_icm45600_state *st);
+
+void inv_icm45600_buffer_update_fifo_period(struct inv_icm45600_state *st);
+
+int inv_icm45600_buffer_set_fifo_en(struct inv_icm45600_state *st,
+				    unsigned int fifo_en);
+
+int inv_icm45600_buffer_update_watermark(struct inv_icm45600_state *st);
+
+int inv_icm45600_buffer_fifo_read(struct inv_icm45600_state *st,
+				  unsigned int max);
+
+int inv_icm45600_buffer_fifo_parse(struct inv_icm45600_state *st);
+
+int inv_icm45600_buffer_hwfifo_flush(struct inv_icm45600_state *st,
+				     unsigned int count);
+
+#endif

-- 
2.34.1



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

* [PATCH 6/8] iio: imu: add Kconfig and Makefile for inv_icm45600 driver
  2025-04-11 13:28 [PATCH 0/8] iio: imu: new inv_icm45600 driver Remi Buisson via B4 Relay
                   ` (4 preceding siblings ...)
  2025-04-11 13:28 ` [PATCH 5/8] iio: imu: inv_icm45600: add buffer support in iio devices Remi Buisson via B4 Relay
@ 2025-04-11 13:28 ` Remi Buisson via B4 Relay
  2025-04-11 17:36   ` Jonathan Cameron
  2025-04-11 13:28 ` [PATCH 7/8] dt-bindings: iio: imu: Add inv_icm45600 documentation Remi Buisson via B4 Relay
  2025-04-11 13:28 ` [PATCH 8/8] MAINTAINERS: add entry for inv_icm45600 6-axis imu sensor Remi Buisson via B4 Relay
  7 siblings, 1 reply; 19+ messages in thread
From: Remi Buisson via B4 Relay @ 2025-04-11 13:28 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-kernel, linux-iio, devicetree, Remi Buisson

From: Remi Buisson <remi.buisson@tdk.com>

Add 4 modules:
- inv-icm45600
- inv-icm45600-i2c
- inv-icm45600-spi
- inv-icm45600-i3c.

Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
---
 drivers/iio/imu/Kconfig               |  1 +
 drivers/iio/imu/Makefile              |  1 +
 drivers/iio/imu/inv_icm45600/Kconfig  | 70 +++++++++++++++++++++++++++++++++++
 drivers/iio/imu/inv_icm45600/Makefile | 17 +++++++++
 4 files changed, 89 insertions(+)

diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig
index 15612f0f189b5114deb414ef840339678abdc562..9d732bed9fcdac12a13713dba3455c1fdf9f4a53 100644
--- a/drivers/iio/imu/Kconfig
+++ b/drivers/iio/imu/Kconfig
@@ -109,6 +109,7 @@ config KMX61
 	  be called kmx61.
 
 source "drivers/iio/imu/inv_icm42600/Kconfig"
+source "drivers/iio/imu/inv_icm45600/Kconfig"
 source "drivers/iio/imu/inv_mpu6050/Kconfig"
 
 config SMI240
diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile
index e901aea498d37e5897e8b71268356a19eac2cb59..2ae6344f84699b2f85fff1c8077cb412f6ae2658 100644
--- a/drivers/iio/imu/Makefile
+++ b/drivers/iio/imu/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_FXOS8700_I2C) += fxos8700_i2c.o
 obj-$(CONFIG_FXOS8700_SPI) += fxos8700_spi.o
 
 obj-y += inv_icm42600/
+obj-y += inv_icm45600/
 obj-y += inv_mpu6050/
 
 obj-$(CONFIG_KMX61) += kmx61.o
diff --git a/drivers/iio/imu/inv_icm45600/Kconfig b/drivers/iio/imu/inv_icm45600/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..4fade8852a0dcf54df2bbd67b9269ed2c59f8699
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/Kconfig
@@ -0,0 +1,70 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+config INV_ICM45600
+	tristate
+	select IIO_BUFFER
+	select IIO_KFIFO_BUF
+	select IIO_INV_SENSORS_TIMESTAMP
+
+config INV_ICM45600_I2C
+	tristate "InvenSense ICM-456xx I2C driver"
+	depends on I2C
+	select INV_ICM45600
+	select REGMAP_I2C
+	help
+	  This driver supports the InvenSense ICM-456xx motion tracking
+	  devices over I2C.
+	  Supported devices:
+	  - ICM-45605
+	  - ICM-45686
+	  - ICM-45688-P
+	  - ICM-45608
+	  - ICM-45634
+	  - ICM-45689
+	  - ICM-45606
+	  - ICM-45687
+
+	  This driver can be built as a module. The module will be called
+	  inv-icm45600-i2c.
+
+config INV_ICM45600_SPI
+	tristate "InvenSense ICM-456xx SPI driver"
+	depends on SPI_MASTER
+	select INV_ICM45600
+	select REGMAP_SPI
+	help
+	  This driver supports the InvenSense ICM-456xx motion tracking
+	  devices over SPI.
+	  Supported devices:
+	  - ICM-45605
+	  - ICM-45686
+	  - ICM-45688-P
+	  - ICM-45608
+	  - ICM-45634
+	  - ICM-45689
+	  - ICM-45606
+	  - ICM-45687
+
+	  This driver can be built as a module. The module will be called
+	  inv-icm45600-spi.
+
+config INV_ICM45600_I3C
+	tristate "InvenSense ICM-456xx I3C driver"
+	depends on I3C
+	select INV_ICM45600
+	select REGMAP_I3C
+	help
+	  This driver supports the InvenSense ICM-456xx motion tracking
+	  devices over I3C.
+	  Supported devices:
+	  - ICM-45605
+	  - ICM-45686
+	  - ICM-45688-P
+	  - ICM-45608
+	  - ICM-45634
+	  - ICM-45689
+	  - ICM-45606
+	  - ICM-45687
+
+	  This driver can be built as a module. The module will be called
+	  inv-icm45600-i3c.
diff --git a/drivers/iio/imu/inv_icm45600/Makefile b/drivers/iio/imu/inv_icm45600/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..0fd6fbce0286f24504dbfe71925f9c35e717c446
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/Makefile
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+obj-$(CONFIG_INV_ICM45600) += inv-icm45600.o
+inv-icm45600-y += inv_icm45600_core.o
+inv-icm45600-y += inv_icm45600_gyro.o
+inv-icm45600-y += inv_icm45600_accel.o
+inv-icm45600-y += inv_icm45600_temp.o
+inv-icm45600-y += inv_icm45600_buffer.o
+
+obj-$(CONFIG_INV_ICM45600_I2C) += inv-icm45600-i2c.o
+inv-icm45600-i2c-y += inv_icm45600_i2c.o
+
+obj-$(CONFIG_INV_ICM45600_SPI) += inv-icm45600-spi.o
+inv-icm45600-spi-y += inv_icm45600_spi.o
+
+obj-$(CONFIG_INV_ICM45600_I3C) += inv-icm45600-i3c.o
+inv-icm45600-i3c-y += inv_icm45600_i3c.o

-- 
2.34.1



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

* [PATCH 7/8] dt-bindings: iio: imu: Add inv_icm45600 documentation
  2025-04-11 13:28 [PATCH 0/8] iio: imu: new inv_icm45600 driver Remi Buisson via B4 Relay
                   ` (5 preceding siblings ...)
  2025-04-11 13:28 ` [PATCH 6/8] iio: imu: add Kconfig and Makefile for inv_icm45600 driver Remi Buisson via B4 Relay
@ 2025-04-11 13:28 ` Remi Buisson via B4 Relay
  2025-04-11 14:39   ` Rob Herring (Arm)
                     ` (3 more replies)
  2025-04-11 13:28 ` [PATCH 8/8] MAINTAINERS: add entry for inv_icm45600 6-axis imu sensor Remi Buisson via B4 Relay
  7 siblings, 4 replies; 19+ messages in thread
From: Remi Buisson via B4 Relay @ 2025-04-11 13:28 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-kernel, linux-iio, devicetree, Remi Buisson

From: Remi Buisson <remi.buisson@tdk.com>

Document the ICM-456xxx devices devicetree bindings.
Describe custom sysfs API for controlling the power modes.

Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
---
 .../ABI/testing/sysfs-bus-iio-inv_icm45600         |  37 ++++++
 .../bindings/iio/imu/invensense,icm45600.yaml      | 136 +++++++++++++++++++++
 2 files changed, 173 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600 b/Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600
new file mode 100644
index 0000000000000000000000000000000000000000..8d2d9b68ad9e35fe0d6c157e984afc327eab92ec
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600
@@ -0,0 +1,37 @@
+What:		/sys/bus/iio/devices/iio:deviceX/in_accel_power_mode
+KernelVersion:	6.16
+Contact:	linux-iio@vger.kernel.org
+Description:
+		Accelerometer power mode. Setting this attribute will set the
+		requested power mode to use if the ODR support it. If ODR
+		support only 1 mode, power mode will be enforced.
+		Reading this attribute will return the current accelerometer
+		power mode if the sensor is on, or the requested value if the
+		sensor is off. The value between real and requested value can
+		be different for ODR supporting only 1 mode.
+
+What:		/sys/bus/iio/devices/iio:deviceX/in_accel_power_mode_available
+KernelVersion:	6.16
+Contact:	linux-iio@vger.kernel.org
+Description:
+		List of available accelerometer power modes that can be set in
+		in_accel_power_mode attribute.
+
+What:		/sys/bus/iio/devices/iio:deviceX/in_anglvel_power_mode
+KernelVersion:	6.16
+Contact:	linux-iio@vger.kernel.org
+Description:
+		Gyroscope power mode. Setting this attribute will set the
+		requested power mode to use if the ODR support it. If ODR
+		support only 1 mode, power mode will be enforced.
+		Reading this attribute will return the current gyroscope
+		power mode if the sensor is on, or the requested value if the
+		sensor is off. The value between real and requested value can
+		be different for ODR supporting only 1 mode.
+
+What:		/sys/bus/iio/devices/iio:deviceX/in_anglvel_power_mode_available
+KernelVersion:	6.16
+Contact:	linux-iio@vger.kernel.org
+Description:
+		List of available gyroscope power modes that can be set in
+		in_anglvel_power_mode attribute.
diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..51455f0b5cb90abdd823f154e45891ad364296e6
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml
@@ -0,0 +1,136 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/imu/invensense,icm45600.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: InvenSense ICM-456xx Inertial Measurement Unit
+
+maintainers:
+  - Remi Buisson <remi.buisson@tdk.com>
+
+description: |
+  6-axis MotionTracking device that combines a 3-axis gyroscope and a 3-axis
+  accelerometer.
+
+  It has a configurable host interface that supports I3C, I2C and SPI serial
+  communication, features up to 8kB FIFO and 2 programmable interrupts with
+  ultra-low-power wake-on-motion support to minimize system power consumption.
+
+  Other industry-leading features include InvenSense on-chip APEX Motion
+  Processing engine for gesture recognition, activity classification, and
+  pedometer, along with programmable digital filters, and an embedded
+  temperature sensor.
+
+  https://invensense.tdk.com/wp-content/uploads/documentation/DS-000576_ICM-45605.pdf
+
+properties:
+  compatible:
+    enum:
+      - invensense,icm45605
+      - invensense,icm45686
+      - invensense,icm45688p
+      - invensense,icm45608
+      - invensense,icm45634
+      - invensense,icm45689
+      - invensense,icm45606
+      - invensense,icm45687
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    minItems: 1
+    maxItems: 2
+
+  interrupt-names:
+    minItems: 1
+    maxItems: 2
+    items:
+      enum:
+        - INT1
+        - INT2
+    description: Choose chip interrupt pin to be used as interrupt input.
+
+  drive-open-drain:
+    type: boolean
+
+  vdd-supply:
+    description: Regulator that provides power to the sensor
+
+  vddio-supply:
+    description: Regulator that provides power to the bus
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - interrupt-names
+
+allOf:
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        icm45605@68 {
+            compatible = "invensense,icm45605";
+            reg = <0x68>;
+            interrupt-parent = <&gpio2>;
+            interrupt-names = "INT1";
+            interrupts = <7 IRQ_TYPE_EDGE_RAISING>;
+            vdd-supply = <&vdd>;
+            vddio-supply = <&vddio>;
+            mount-matrix = "1", "0", "0",
+                           "0", "1", "0",
+                           "0", "0", "1";
+        };
+    };
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        icm45605@0 {
+            compatible = "invensense,icm45605";
+            reg = <0>;
+            spi-max-frequency = <24000000>;
+            interrupt-parent = <&gpio1>;
+            interrupt-names = "INT1";
+            interrupts = <6 IRQ_TYPE_EDGE_RAISING>;
+            vdd-supply = <&vdd>;
+            vddio-supply = <&vddio>;
+            mount-matrix = "1", "0", "0",
+                           "0", "1", "0",
+                           "0", "0", "1";
+        };
+    };
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    i3c {
+        #address-cells = <3>;
+        #size-cells = <0>;
+
+        icm45600@68,46A00000011 {
+            compatible = "invensense,icm45600";
+            reg = <0x68 0x46A 0x84>;
+            interrupt-parent = <&gpio1>;
+            interrupt-names = "INT1";
+            interrupts = <5 IRQ_TYPE_EDGE_RISING>;
+            vdd-supply = <&vdd>;
+            vddio-supply = <&vddio>;
+            mount-matrix = "1", "0", "0",
+                           "0", "1", "0",
+                           "0", "0", "1";
+        };
+    };

-- 
2.34.1



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

* [PATCH 8/8] MAINTAINERS: add entry for inv_icm45600 6-axis imu sensor
  2025-04-11 13:28 [PATCH 0/8] iio: imu: new inv_icm45600 driver Remi Buisson via B4 Relay
                   ` (6 preceding siblings ...)
  2025-04-11 13:28 ` [PATCH 7/8] dt-bindings: iio: imu: Add inv_icm45600 documentation Remi Buisson via B4 Relay
@ 2025-04-11 13:28 ` Remi Buisson via B4 Relay
  7 siblings, 0 replies; 19+ messages in thread
From: Remi Buisson via B4 Relay @ 2025-04-11 13:28 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-kernel, linux-iio, devicetree, Remi Buisson

From: Remi Buisson <remi.buisson@tdk.com>

Add MAINTAINERS entry for InvenSense ICM-456xx IMU device.

Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
---
 MAINTAINERS | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 030d90d383411bbfe949cfff4f5bce27e3dd37c4..2d4b6755a9e240432ec1abfb131358ae4df48362 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12397,6 +12397,15 @@ F:	Documentation/ABI/testing/sysfs-bus-iio-inv_icm42600
 F:	Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml
 F:	drivers/iio/imu/inv_icm42600/
 
+INVENSENSE ICM-456xx IMU DRIVER
+M:	Remi Buisson <remi.buisson@tdk.com>
+L:	linux-iio@vger.kernel.org
+S:	Maintained
+W:	https://invensense.tdk.com/
+F:	Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600
+F:	Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml
+F:	drivers/iio/imu/inv_icm45600/
+
 INVENSENSE MPU-3050 GYROSCOPE DRIVER
 M:	Linus Walleij <linus.walleij@linaro.org>
 L:	linux-iio@vger.kernel.org

-- 
2.34.1



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

* Re: [PATCH 7/8] dt-bindings: iio: imu: Add inv_icm45600 documentation
  2025-04-11 13:28 ` [PATCH 7/8] dt-bindings: iio: imu: Add inv_icm45600 documentation Remi Buisson via B4 Relay
@ 2025-04-11 14:39   ` Rob Herring (Arm)
  2025-04-11 21:16   ` Rob Herring
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 19+ messages in thread
From: Rob Herring (Arm) @ 2025-04-11 14:39 UTC (permalink / raw)
  To: Remi Buisson
  Cc: linux-iio, Conor Dooley, Andy Shevchenko, Nuno Sá,
	devicetree, linux-kernel, David Lechner, Jonathan Cameron,
	Krzysztof Kozlowski


On Fri, 11 Apr 2025 13:28:39 +0000, Remi Buisson wrote:
> Document the ICM-456xxx devices devicetree bindings.
> Describe custom sysfs API for controlling the power modes.
> 
> Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
> ---
>  .../ABI/testing/sysfs-bus-iio-inv_icm45600         |  37 ++++++
>  .../bindings/iio/imu/invensense,icm45600.yaml      | 136 +++++++++++++++++++++
>  2 files changed, 173 insertions(+)
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Error: Documentation/devicetree/bindings/iio/imu/invensense,icm45600.example.dts:35.33-34 syntax error
FATAL ERROR: Unable to parse input tree
make[2]: *** [scripts/Makefile.dtbs:131: Documentation/devicetree/bindings/iio/imu/invensense,icm45600.example.dtb] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [/builds/robherring/dt-review-ci/linux/Makefile:1525: dt_binding_check] Error 2
make: *** [Makefile:248: __sub-make] Error 2

doc reference errors (make refcheckdocs):
[WARNING] /sys/bus/iio/devices/iio:deviceX/in_accel_power_mode is defined 2 times: ./Documentation/ABI/testing/sysfs-bus-iio-inv_icm42600:1; ./Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600:1
[WARNING] /sys/bus/iio/devices/iio:deviceX/in_accel_power_mode_available is defined 2 times: ./Documentation/ABI/testing/sysfs-bus-iio-inv_icm42600:13; ./Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600:13

See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20250411-add_newport_driver-v1-7-15082160b019@tdk.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


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

* Re: [PATCH 6/8] iio: imu: add Kconfig and Makefile for inv_icm45600 driver
  2025-04-11 13:28 ` [PATCH 6/8] iio: imu: add Kconfig and Makefile for inv_icm45600 driver Remi Buisson via B4 Relay
@ 2025-04-11 17:36   ` Jonathan Cameron
  0 siblings, 0 replies; 19+ messages in thread
From: Jonathan Cameron @ 2025-04-11 17:36 UTC (permalink / raw)
  To: Remi Buisson via B4 Relay
  Cc: remi.buisson, Jonathan Cameron, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	linux-kernel, linux-iio, devicetree

On Fri, 11 Apr 2025 13:28:38 +0000
Remi Buisson via B4 Relay <devnull+remi.buisson.tdk.com@kernel.org> wrote:

> From: Remi Buisson <remi.buisson@tdk.com>
> 
> Add 4 modules:
> - inv-icm45600
> - inv-icm45600-i2c
> - inv-icm45600-spi
> - inv-icm45600-i3c.
> 
> Signed-off-by: Remi Buisson <remi.buisson@tdk.com>

Hi Remi,

One quick comment.  A driver should be structured to build fully
after each patch.  That means the makefile and kconfig should be introduced
in patch 1 of the series. Obviously more stuff will be added as you go
along.

If someone just wants to pick up the patches for the core and one bus
they should be able to do so and expect that to work.

Some other areas of the kernel are more flexible on this than I am
but for IIO at least that's they way we have always done things
and I'm not keen to change it.  It means that we know everything
we need to review each step in sequence is there as otherwise it
would not build.

It does mean that you need to build test each additional patch
whenever you do a new version to be sure that the series will be
fine for a bisection build. 

The exception of 'hidden' library modules like the core one here
that won't get built until someone selects them from another kconfig
symbol is fine though.

Thanks,

Jonathan

> ---
>  drivers/iio/imu/Kconfig               |  1 +
>  drivers/iio/imu/Makefile              |  1 +
>  drivers/iio/imu/inv_icm45600/Kconfig  | 70 +++++++++++++++++++++++++++++++++++
>  drivers/iio/imu/inv_icm45600/Makefile | 17 +++++++++
>  4 files changed, 89 insertions(+)
> 
> diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig
> index 15612f0f189b5114deb414ef840339678abdc562..9d732bed9fcdac12a13713dba3455c1fdf9f4a53 100644
> --- a/drivers/iio/imu/Kconfig
> +++ b/drivers/iio/imu/Kconfig
> @@ -109,6 +109,7 @@ config KMX61
>  	  be called kmx61.
>  
>  source "drivers/iio/imu/inv_icm42600/Kconfig"
> +source "drivers/iio/imu/inv_icm45600/Kconfig"
>  source "drivers/iio/imu/inv_mpu6050/Kconfig"
>  
>  config SMI240
> diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile
> index e901aea498d37e5897e8b71268356a19eac2cb59..2ae6344f84699b2f85fff1c8077cb412f6ae2658 100644
> --- a/drivers/iio/imu/Makefile
> +++ b/drivers/iio/imu/Makefile
> @@ -25,6 +25,7 @@ obj-$(CONFIG_FXOS8700_I2C) += fxos8700_i2c.o
>  obj-$(CONFIG_FXOS8700_SPI) += fxos8700_spi.o
>  
>  obj-y += inv_icm42600/
> +obj-y += inv_icm45600/
>  obj-y += inv_mpu6050/
>  
>  obj-$(CONFIG_KMX61) += kmx61.o
> diff --git a/drivers/iio/imu/inv_icm45600/Kconfig b/drivers/iio/imu/inv_icm45600/Kconfig
> new file mode 100644
> index 0000000000000000000000000000000000000000..4fade8852a0dcf54df2bbd67b9269ed2c59f8699
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm45600/Kconfig
> @@ -0,0 +1,70 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +
> +config INV_ICM45600
> +	tristate
> +	select IIO_BUFFER
> +	select IIO_KFIFO_BUF
> +	select IIO_INV_SENSORS_TIMESTAMP
> +
> +config INV_ICM45600_I2C
> +	tristate "InvenSense ICM-456xx I2C driver"
> +	depends on I2C
> +	select INV_ICM45600
> +	select REGMAP_I2C
> +	help
> +	  This driver supports the InvenSense ICM-456xx motion tracking
> +	  devices over I2C.
> +	  Supported devices:
> +	  - ICM-45605
> +	  - ICM-45686
> +	  - ICM-45688-P
> +	  - ICM-45608
> +	  - ICM-45634
> +	  - ICM-45689
> +	  - ICM-45606
> +	  - ICM-45687
> +
> +	  This driver can be built as a module. The module will be called
> +	  inv-icm45600-i2c.
> +
> +config INV_ICM45600_SPI
> +	tristate "InvenSense ICM-456xx SPI driver"
> +	depends on SPI_MASTER
> +	select INV_ICM45600
> +	select REGMAP_SPI
> +	help
> +	  This driver supports the InvenSense ICM-456xx motion tracking
> +	  devices over SPI.
> +	  Supported devices:
> +	  - ICM-45605
> +	  - ICM-45686
> +	  - ICM-45688-P
> +	  - ICM-45608
> +	  - ICM-45634
> +	  - ICM-45689
> +	  - ICM-45606
> +	  - ICM-45687
> +
> +	  This driver can be built as a module. The module will be called
> +	  inv-icm45600-spi.
> +
> +config INV_ICM45600_I3C
> +	tristate "InvenSense ICM-456xx I3C driver"
> +	depends on I3C
> +	select INV_ICM45600
> +	select REGMAP_I3C
> +	help
> +	  This driver supports the InvenSense ICM-456xx motion tracking
> +	  devices over I3C.
> +	  Supported devices:
> +	  - ICM-45605
> +	  - ICM-45686
> +	  - ICM-45688-P
> +	  - ICM-45608
> +	  - ICM-45634
> +	  - ICM-45689
> +	  - ICM-45606
> +	  - ICM-45687
> +
> +	  This driver can be built as a module. The module will be called
> +	  inv-icm45600-i3c.
> diff --git a/drivers/iio/imu/inv_icm45600/Makefile b/drivers/iio/imu/inv_icm45600/Makefile
> new file mode 100644
> index 0000000000000000000000000000000000000000..0fd6fbce0286f24504dbfe71925f9c35e717c446
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm45600/Makefile
> @@ -0,0 +1,17 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +
> +obj-$(CONFIG_INV_ICM45600) += inv-icm45600.o
> +inv-icm45600-y += inv_icm45600_core.o
> +inv-icm45600-y += inv_icm45600_gyro.o
> +inv-icm45600-y += inv_icm45600_accel.o
> +inv-icm45600-y += inv_icm45600_temp.o
> +inv-icm45600-y += inv_icm45600_buffer.o
> +
> +obj-$(CONFIG_INV_ICM45600_I2C) += inv-icm45600-i2c.o
> +inv-icm45600-i2c-y += inv_icm45600_i2c.o
> +
> +obj-$(CONFIG_INV_ICM45600_SPI) += inv-icm45600-spi.o
> +inv-icm45600-spi-y += inv_icm45600_spi.o
> +
> +obj-$(CONFIG_INV_ICM45600_I3C) += inv-icm45600-i3c.o
> +inv-icm45600-i3c-y += inv_icm45600_i3c.o
> 


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

* Re: [PATCH 2/8] iio: imu: inv_icm45600: add I2C driver for inv_icm45600 driver
  2025-04-11 13:28 ` [PATCH 2/8] iio: imu: inv_icm45600: add I2C driver for " Remi Buisson via B4 Relay
@ 2025-04-11 19:21   ` Andy Shevchenko
  0 siblings, 0 replies; 19+ messages in thread
From: Andy Shevchenko @ 2025-04-11 19:21 UTC (permalink / raw)
  To: remi.buisson
  Cc: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel,
	linux-iio, devicetree

On Fri, Apr 11, 2025 at 4:28 PM Remi Buisson via B4 Relay
<devnull+remi.buisson.tdk.com@kernel.org> wrote:
>
> From: Remi Buisson <remi.buisson@tdk.com>
>
> Add I2C driver for InvenSense ICM-456xxx devices.

...

> +/*
> + * Copyright (C) 2025 InvenSense, Inc.
> + */

One line (instead of 3), please! This will slightly help with these
rather longer files.

...

> +#include <linux/device.h>
> +#include <linux/i2c.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>

Here and in the rest of the files the list of the header inclusions is
(semi-)random. You shouldn't use kernel.h and other "proxy" headers in
the code, please follow IWYU principle.

...

> +               return -ENOTSUPP;

This is wrong. Please check what other drivers return in such cases.

...

> +       regmap = devm_regmap_init_i2c(client, &inv_icm45600_regmap_config);
> +       if (IS_ERR(regmap))
> +               return PTR_ERR(regmap);

err.h is missing.

...

> +/*
> + * device id table is used to identify what device can be
> + * supported by this driver

Please, respect English grammar and punctuation in all multi-line comments.

> + */

...

> +               .data = (void *)INV_CHIP_ICM45688P,

Just no. Please, use real pointers.

...

I'm not going to review anything else, the above already deserves a new version.

-- 
With Best Regards,
Andy Shevchenko

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

* Re: [PATCH 7/8] dt-bindings: iio: imu: Add inv_icm45600 documentation
  2025-04-11 13:28 ` [PATCH 7/8] dt-bindings: iio: imu: Add inv_icm45600 documentation Remi Buisson via B4 Relay
  2025-04-11 14:39   ` Rob Herring (Arm)
@ 2025-04-11 21:16   ` Rob Herring
  2025-04-11 21:18   ` David Lechner
  2025-04-12 18:24   ` Jonathan Cameron
  3 siblings, 0 replies; 19+ messages in thread
From: Rob Herring @ 2025-04-11 21:16 UTC (permalink / raw)
  To: Remi Buisson
  Cc: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-iio,
	devicetree

On Fri, Apr 11, 2025 at 01:28:39PM +0000, Remi Buisson wrote:
> Document the ICM-456xxx devices devicetree bindings.
> Describe custom sysfs API for controlling the power modes.

You can drop 'documentation' from the subject.

> 
> Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
> ---
>  .../ABI/testing/sysfs-bus-iio-inv_icm45600         |  37 ++++++

This goes in the patch adding the sysfs files.

>  .../bindings/iio/imu/invensense,icm45600.yaml      | 136 +++++++++++++++++++++
>  2 files changed, 173 insertions(+)


> diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml
> new file mode 100644
> index 0000000000000000000000000000000000000000..51455f0b5cb90abdd823f154e45891ad364296e6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml
> @@ -0,0 +1,136 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/imu/invensense,icm45600.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: InvenSense ICM-456xx Inertial Measurement Unit
> +
> +maintainers:
> +  - Remi Buisson <remi.buisson@tdk.com>
> +
> +description: |
> +  6-axis MotionTracking device that combines a 3-axis gyroscope and a 3-axis
> +  accelerometer.
> +
> +  It has a configurable host interface that supports I3C, I2C and SPI serial
> +  communication, features up to 8kB FIFO and 2 programmable interrupts with
> +  ultra-low-power wake-on-motion support to minimize system power consumption.
> +
> +  Other industry-leading features include InvenSense on-chip APEX Motion
> +  Processing engine for gesture recognition, activity classification, and
> +  pedometer, along with programmable digital filters, and an embedded
> +  temperature sensor.
> +
> +  https://invensense.tdk.com/wp-content/uploads/documentation/DS-000576_ICM-45605.pdf
> +
> +properties:
> +  compatible:
> +    enum:
> +      - invensense,icm45605
> +      - invensense,icm45686
> +      - invensense,icm45688p
> +      - invensense,icm45608
> +      - invensense,icm45634
> +      - invensense,icm45689
> +      - invensense,icm45606
> +      - invensense,icm45687

sort in alpanumeric order

> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    minItems: 1
> +    maxItems: 2
> +
> +  interrupt-names:
> +    minItems: 1
> +    maxItems: 2
> +    items:
> +      enum:
> +        - INT1
> +        - INT2
> +    description: Choose chip interrupt pin to be used as interrupt input.
> +
> +  drive-open-drain:
> +    type: boolean
> +
> +  vdd-supply:
> +    description: Regulator that provides power to the sensor
> +
> +  vddio-supply:
> +    description: Regulator that provides power to the bus
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - interrupt-names
> +
> +allOf:
> +  - $ref: /schemas/spi/spi-peripheral-props.yaml#
> +
> +unevaluatedProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        icm45605@68 {
> +            compatible = "invensense,icm45605";
> +            reg = <0x68>;
> +            interrupt-parent = <&gpio2>;
> +            interrupt-names = "INT1";
> +            interrupts = <7 IRQ_TYPE_EDGE_RAISING>;
> +            vdd-supply = <&vdd>;
> +            vddio-supply = <&vddio>;
> +            mount-matrix = "1", "0", "0",
> +                           "0", "1", "0",
> +                           "0", "0", "1";
> +        };
> +    };
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    spi {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        icm45605@0 {
> +            compatible = "invensense,icm45605";
> +            reg = <0>;
> +            spi-max-frequency = <24000000>;
> +            interrupt-parent = <&gpio1>;
> +            interrupt-names = "INT1";
> +            interrupts = <6 IRQ_TYPE_EDGE_RAISING>;
> +            vdd-supply = <&vdd>;
> +            vddio-supply = <&vddio>;
> +            mount-matrix = "1", "0", "0",
> +                           "0", "1", "0",
> +                           "0", "0", "1";
> +        };
> +    };
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    i3c {
> +        #address-cells = <3>;
> +        #size-cells = <0>;
> +
> +        icm45600@68,46A00000011 {
> +            compatible = "invensense,icm45600";
> +            reg = <0x68 0x46A 0x84>;
> +            interrupt-parent = <&gpio1>;
> +            interrupt-names = "INT1";
> +            interrupts = <5 IRQ_TYPE_EDGE_RISING>;
> +            vdd-supply = <&vdd>;
> +            vddio-supply = <&vddio>;
> +            mount-matrix = "1", "0", "0",
> +                           "0", "1", "0",
> +                           "0", "0", "1";
> +        };
> +    };
> 
> -- 
> 2.34.1
> 

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

* Re: [PATCH 7/8] dt-bindings: iio: imu: Add inv_icm45600 documentation
  2025-04-11 13:28 ` [PATCH 7/8] dt-bindings: iio: imu: Add inv_icm45600 documentation Remi Buisson via B4 Relay
  2025-04-11 14:39   ` Rob Herring (Arm)
  2025-04-11 21:16   ` Rob Herring
@ 2025-04-11 21:18   ` David Lechner
  2025-04-12 18:17     ` Jonathan Cameron
  2025-04-12 18:24   ` Jonathan Cameron
  3 siblings, 1 reply; 19+ messages in thread
From: David Lechner @ 2025-04-11 21:18 UTC (permalink / raw)
  To: remi.buisson, Jonathan Cameron, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-kernel, linux-iio, devicetree

On 4/11/25 8:28 AM, Remi Buisson via B4 Relay wrote:
> From: Remi Buisson <remi.buisson@tdk.com>
> 
> Document the ICM-456xxx devices devicetree bindings.
> Describe custom sysfs API for controlling the power modes.
> 
> Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
> ---

IMHO, it is more logical to have the dt-bindings patch first in the series
before the code that uses it.

>  .../ABI/testing/sysfs-bus-iio-inv_icm45600         |  37 ++++++
>  .../bindings/iio/imu/invensense,icm45600.yaml      | 136 +++++++++++++++++++++
>  2 files changed, 173 insertions(+)
> 
> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600 b/Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600
> new file mode 100644
> index 0000000000000000000000000000000000000000..8d2d9b68ad9e35fe0d6c157e984afc327eab92ec
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600


ABI documentation is separate from dt-bindings and needs to go in a
different patch.

Also, it looks like /sys/.../iio:deviceX/in_accelY_power_mode is
already a standard attribute in Documentation/ABI/testing/sysfs-bus-iio
so we could add to that instead of creating a new file.

And there is Documentation/ABI/testing/sysfs-bus-iio-inv_icm42600
that has the same attribute essentially. So it would be good to
delete this file and consolidate everything in the main file.

> diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml
> new file mode 100644
> index 0000000000000000000000000000000000000000..51455f0b5cb90abdd823f154e45891ad364296e6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml
> @@ -0,0 +1,136 @@

...

> +allOf:
> +  - $ref: /schemas/spi/spi-peripheral-props.yaml#

Since this can be connected to different buses, I don't think we want
to always ref this. It gets included implicitly for all child nodes on a
spi controller node anyway.


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

* Re: [PATCH 7/8] dt-bindings: iio: imu: Add inv_icm45600 documentation
  2025-04-11 21:18   ` David Lechner
@ 2025-04-12 18:17     ` Jonathan Cameron
  0 siblings, 0 replies; 19+ messages in thread
From: Jonathan Cameron @ 2025-04-12 18:17 UTC (permalink / raw)
  To: David Lechner
  Cc: remi.buisson, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-iio,
	devicetree

On Fri, 11 Apr 2025 16:18:39 -0500
David Lechner <dlechner@baylibre.com> wrote:

> On 4/11/25 8:28 AM, Remi Buisson via B4 Relay wrote:
> > From: Remi Buisson <remi.buisson@tdk.com>
> > 
> > Document the ICM-456xxx devices devicetree bindings.
> > Describe custom sysfs API for controlling the power modes.
> > 
> > Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
> > ---  
> 
> IMHO, it is more logical to have the dt-bindings patch first in the series
> before the code that uses it.
> 
> >  .../ABI/testing/sysfs-bus-iio-inv_icm45600         |  37 ++++++
> >  .../bindings/iio/imu/invensense,icm45600.yaml      | 136 +++++++++++++++++++++
> >  2 files changed, 173 insertions(+)
> > 
> > diff --git a/Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600 b/Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..8d2d9b68ad9e35fe0d6c157e984afc327eab92ec
> > --- /dev/null
> > +++ b/Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600  
> 
> 
> ABI documentation is separate from dt-bindings and needs to go in a
> different patch.
> 
> Also, it looks like /sys/.../iio:deviceX/in_accelY_power_mode is
> already a standard attribute in Documentation/ABI/testing/sysfs-bus-iio
> so we could add to that instead of creating a new file.

Just a quick side note.  Those powermode interfaces are hard for userspace
to reason about, so where possible it is better to set power mode in response
to more explicit demands such as sampling frequency.  I can't remember when
I got persuaded to let that one in. I checked - long ago and we still only
have 2 users :)

> 
> And there is Documentation/ABI/testing/sysfs-bus-iio-inv_icm42600
> that has the same attribute essentially. So it would be good to
> delete this file and consolidate everything in the main file.
> 
> > diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..51455f0b5cb90abdd823f154e45891ad364296e6
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml
> > @@ -0,0 +1,136 @@  
> 
> ...
> 
> > +allOf:
> > +  - $ref: /schemas/spi/spi-peripheral-props.yaml#  
> 
> Since this can be connected to different buses, I don't think we want
> to always ref this. It gets included implicitly for all child nodes on a
> spi controller node anyway.
> 


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

* Re: [PATCH 7/8] dt-bindings: iio: imu: Add inv_icm45600 documentation
  2025-04-11 13:28 ` [PATCH 7/8] dt-bindings: iio: imu: Add inv_icm45600 documentation Remi Buisson via B4 Relay
                     ` (2 preceding siblings ...)
  2025-04-11 21:18   ` David Lechner
@ 2025-04-12 18:24   ` Jonathan Cameron
  3 siblings, 0 replies; 19+ messages in thread
From: Jonathan Cameron @ 2025-04-12 18:24 UTC (permalink / raw)
  To: Remi Buisson via B4 Relay
  Cc: remi.buisson, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel,
	linux-iio, devicetree

On Fri, 11 Apr 2025 13:28:39 +0000
Remi Buisson via B4 Relay <devnull+remi.buisson.tdk.com@kernel.org> wrote:

> From: Remi Buisson <remi.buisson@tdk.com>
> 
> Document the ICM-456xxx devices devicetree bindings.
> Describe custom sysfs API for controlling the power modes.
> 
> Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
> ---
>  .../ABI/testing/sysfs-bus-iio-inv_icm45600         |  37 ++++++
>  .../bindings/iio/imu/invensense,icm45600.yaml      | 136 +++++++++++++++++++++
>  2 files changed, 173 insertions(+)
> 
> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600 b/Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600
> new file mode 100644
> index 0000000000000000000000000000000000000000..8d2d9b68ad9e35fe0d6c157e984afc327eab92ec
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-bus-iio-inv_icm45600
> @@ -0,0 +1,37 @@

As has been noted, wrong place. This goes in the patch with the relevant ABI
being added to the driver.

> +What:		/sys/bus/iio/devices/iio:deviceX/in_accel_power_mode
> +KernelVersion:	6.16
> +Contact:	linux-iio@vger.kernel.org
> +Description:
> +		Accelerometer power mode. Setting this attribute will set the
> +		requested power mode to use if the ODR support it. If ODR
> +		support only 1 mode, power mode will be enforced.
If the mode id enforced, then the ODR will change?  I hope not. ODR should dominate here
though note you should match ABI terms anyway so sampling_frequency.

Also as noted, these already exist in some form in the main docs. Add entries
there.

> +		Reading this attribute will return the current accelerometer
> +		power mode if the sensor is on, or the requested value if the
> +		sensor is off. The value between real and requested value can
> +		be different for ODR supporting only 1 mode.

I'd just fail the set. If the ODR is changed in a fashion that requires this
to change, just do it and have this attribute return the new value. All ABI
is allowed when necessary to have side effects on other IIO ABI elements.
We try to keep that as intuitive as possible, but sometimes the hardware
puts very complex requirements on us.

> +
> +What:		/sys/bus/iio/devices/iio:deviceX/in_accel_power_mode_available
> +KernelVersion:	6.16
> +Contact:	linux-iio@vger.kernel.org
> +Description:
> +		List of available accelerometer power modes that can be set in
> +		in_accel_power_mode attribute.
> +
> +What:		/sys/bus/iio/devices/iio:deviceX/in_anglvel_power_mode
> +KernelVersion:	6.16
> +Contact:	linux-iio@vger.kernel.org
> +Description:
> +		Gyroscope power mode. Setting this attribute will set the
> +		requested power mode to use if the ODR support it. If ODR
> +		support only 1 mode, power mode will be enforced.
> +		Reading this attribute will return the current gyroscope
> +		power mode if the sensor is on, or the requested value if the
> +		sensor is off. The value between real and requested value can
> +		be different for ODR supporting only 1 mode.
> +
> +What:		/sys/bus/iio/devices/iio:deviceX/in_anglvel_power_mode_available
> +KernelVersion:	6.16
> +Contact:	linux-iio@vger.kernel.org
> +Description:
> +		List of available gyroscope power modes that can be set in
> +		in_anglvel_power_mode attribute.


> diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml
> new file mode 100644
> index 0000000000000000000000000000000000000000..51455f0b5cb90abdd823f154e45891ad364296e6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml
> @@ -0,0 +1,136 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/imu/invensense,icm45600.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: InvenSense ICM-456xx Inertial Measurement Unit
I'd go with Invensense ICM-45600 and similar Inertial Measurement Units.

We have been bitten too many times in the past by wild cards and manufacturers
who love to put something in a numbering scheme for marketing reasons (or just
because they feel like it) rather than because they are in any way related
at the hardware level.

> +
> +maintainers:
> +  - Remi Buisson <remi.buisson@tdk.com>
> +
> +description: |
> +  6-axis MotionTracking device that combines a 3-axis gyroscope and a 3-axis
> +  accelerometer.
> +
> +  It has a configurable host interface that supports I3C, I2C and SPI serial
> +  communication, features up to 8kB FIFO and 2 programmable interrupts with
> +  ultra-low-power wake-on-motion support to minimize system power consumption.
> +
> +  Other industry-leading features include InvenSense on-chip APEX Motion
> +  Processing engine for gesture recognition, activity classification, and
> +  pedometer, along with programmable digital filters, and an embedded
> +  temperature sensor.
> +
> +  https://invensense.tdk.com/wp-content/uploads/documentation/DS-000576_ICM-45605.pdf
> +
> +properties:
> +  compatible:
> +    enum:
> +      - invensense,icm45605

Alpha numeric order for these.

> +      - invensense,icm45686
> +      - invensense,icm45688p
> +      - invensense,icm45608
> +      - invensense,icm45634
> +      - invensense,icm45689
> +      - invensense,icm45606
> +      - invensense,icm45687
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    minItems: 1
> +    maxItems: 2
> +
> +  interrupt-names:
> +    minItems: 1
> +    maxItems: 2
> +    items:
> +      enum:
> +        - INT1
> +        - INT2
> +    description: Choose chip interrupt pin to be used as interrupt input.
> +
> +  drive-open-drain:
> +    type: boolean
> +
> +  vdd-supply:
> +    description: Regulator that provides power to the sensor
For really 'standard' supplies like these it is also acceptable to just
do
    vdd-supply: true
    vddio-supply: true

When description adds little there is no need to give one.

> +
> +  vddio-supply:
> +    description: Regulator that provides power to the bus
> +
I think you need
    mount-matrix: true
as well.

> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - interrupt-names
> +
> +allOf:
> +  - $ref: /schemas/spi/spi-peripheral-props.yaml#
> +
> +unevaluatedProperties: false

> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        icm45605@68 {
> +            compatible = "invensense,icm45605";
> +            reg = <0x68>;
> +            interrupt-parent = <&gpio2>;
> +            interrupt-names = "INT1";
> +            interrupts = <7 IRQ_TYPE_EDGE_RAISING>;
> +            vdd-supply = <&vdd>;
> +            vddio-supply = <&vddio>;
> +            mount-matrix = "1", "0", "0",
> +                           "0", "1", "0",
> +                           "0", "0", "1";
> +        };
> +    };
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    spi {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        icm45605@0 {
> +            compatible = "invensense,icm45605";
> +            reg = <0>;
> +            spi-max-frequency = <24000000>;
> +            interrupt-parent = <&gpio1>;
> +            interrupt-names = "INT1";
> +            interrupts = <6 IRQ_TYPE_EDGE_RAISING>;
> +            vdd-supply = <&vdd>;
> +            vddio-supply = <&vddio>;
> +            mount-matrix = "1", "0", "0",
> +                           "0", "1", "0",
> +                           "0", "0", "1";
> +        };
> +    };
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    i3c {
> +        #address-cells = <3>;
> +        #size-cells = <0>;
> +
> +        icm45600@68,46A00000011 {
> +            compatible = "invensense,icm45600";
> +            reg = <0x68 0x46A 0x84>;
> +            interrupt-parent = <&gpio1>;
> +            interrupt-names = "INT1";
> +            interrupts = <5 IRQ_TYPE_EDGE_RISING>;
> +            vdd-supply = <&vdd>;
> +            vddio-supply = <&vddio>;
> +            mount-matrix = "1", "0", "0",
> +                           "0", "1", "0",
> +                           "0", "0", "1";
> +        };
> +    };
> 


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

* Re: [PATCH 1/8] iio: imu: inv_icm45600: add new inv_icm45600 driver
  2025-04-11 13:28 ` [PATCH 1/8] iio: imu: inv_icm45600: add " Remi Buisson via B4 Relay
@ 2025-04-12 19:10   ` Jonathan Cameron
  0 siblings, 0 replies; 19+ messages in thread
From: Jonathan Cameron @ 2025-04-12 19:10 UTC (permalink / raw)
  To: Remi Buisson via B4 Relay
  Cc: remi.buisson, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel,
	linux-iio, devicetree

On Fri, 11 Apr 2025 13:28:33 +0000
Remi Buisson via B4 Relay <devnull+remi.buisson.tdk.com@kernel.org> wrote:

> From: Remi Buisson <remi.buisson@tdk.com>
> 
> Core component of a new driver for InvenSense ICM-456xx devices.
> It includes:
> - registers definition, main probe/setup, and device
> utility functions.
> - IIO device for gyroscope sensor with data polling interface.
> Attributes: raw, scale, sampling_frequency, calibbias.
> - IIO device for gyroscope sensor with data polling interface.
> Attributes: raw, scale, sampling_frequency, calibbias.
> - Temperature is available as a processed
> channel
> 
> ICM-456xx devices are latest generation of 6-axis IMU,
> gyroscope+accelerometer and temperature sensor. This device
> includes a 8K FIFO, supports I2C/I3C/SPI, and provides
> intelligent motion features like pedometer, tilt detection,
> and tap detection.
> 
> Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
> ---
>  drivers/iio/imu/inv_icm45600/inv_icm45600.h       | 421 ++++++++++
>  drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c | 902 +++++++++++++++++++++
>  drivers/iio/imu/inv_icm45600/inv_icm45600_core.c  | 906 +++++++++++++++++++++
>  drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c  | 919 ++++++++++++++++++++++

Too big. Build this up in stages.  Rule of thumb is don't go over 1000 lines in a
single patch without very good reason.  Until you break it up review is likely
to be rather superficial (as this one is!)

1st patch should just have the core part but it should be complete and buildable
2-4 patches introduce the other driver types.

Code is in pretty good shape from a first look. Various minor things inline.

Jonathan


>  drivers/iio/imu/inv_icm45600/inv_icm45600_temp.c  |  82 ++
>  drivers/iio/imu/inv_icm45600/inv_icm45600_temp.h  |  31 +
>  6 files changed, 3261 insertions(+)
> 
> diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600.h b/drivers/iio/imu/inv_icm45600/inv_icm45600.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..6d10b3ffabbcbc054986d6cc0011891863b74e1a
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600.h
> @@ -0,0 +1,421 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2025 Invensense, Inc.
> + */
> +
> +#ifndef INV_ICM45600_H_
> +#define INV_ICM45600_H_
> +
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/iio/common/inv_sensors_timestamp.h>
> +#include <linux/iio/iio.h>
> +#include <linux/mutex.h>
> +#include <linux/pm.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
This is a lot to find in the way of includes in a header.
Where possible use forward defines for any pointer types and push
as many of these includes as possible down into the c files where
I'm assuming they are mostly relevant.

> +
> +#include "inv_icm45600_buffer.h"
> +
> +#define INV_ICM45600_REG_GET_BANK(_r)	FIELD_GET(GENMASK(15, 8), (_r))
> +#define INV_ICM45600_REG_GET_ADDR(_r)	FIELD_GET(GENMASK(7, 0), (_r))
> +
> +enum inv_icm45600_chip {
> +	INV_CHIP_INVALID,
Why?  
> +	INV_CHIP_ICM45605,
> +	INV_CHIP_ICM45686,
> +	INV_CHIP_ICM45688P,
> +	INV_CHIP_ICM45608,
> +	INV_CHIP_ICM45634,
> +	INV_CHIP_ICM45689,
> +	INV_CHIP_ICM45606,
> +	INV_CHIP_ICM45687,
> +	INV_CHIP_NB,
No comma on a terminating entry like this.  It always needs to be at the end.
> +};

In general the presence of an enum for the supported chips often indicates
that the driver is doing things in code that should be data.  Basically move
all the device specific stuff into a chip specific config structure.  That
can use callbacks if necessary, but static const data preferred where possible.

That approach has many times proven to be more scalable and readable as it
brings all the subtleties of a given chip together in one place.

> +
> +enum inv_icm45600_sensor_mode {
> +	INV_ICM45600_SENSOR_MODE_OFF,
> +	INV_ICM45600_SENSOR_MODE_STANDBY,
> +	INV_ICM45600_SENSOR_MODE_LOW_POWER,
> +	INV_ICM45600_SENSOR_MODE_LOW_NOISE,
> +	INV_ICM45600_SENSOR_MODE_NB,

Similar to above. If you have a numbering entry, no trailing comma
as we want it to be the last element.

> +};
> +
> +/* gyroscope fullscale values */
> +enum inv_icm45600_gyro_fs {
Given you have a gyroscope specific file, can we not push this
down into that c file rather than the header?

Try to minimize what is in the shared header.

> +	INV_ICM45600_GYRO_FS_2000DPS,
> +	INV_ICM45600_GYRO_FS_1000DPS,
> +	INV_ICM45600_GYRO_FS_500DPS,
> +	INV_ICM45600_GYRO_FS_250DPS,
> +	INV_ICM45600_GYRO_FS_125DPS,
> +	INV_ICM45600_GYRO_FS_62_5DPS,
> +	INV_ICM45600_GYRO_FS_31_25DPS,
> +	INV_ICM45600_GYRO_FS_15_625DPS,
> +	INV_ICM45600_GYRO_FS_NB,
> +};

Blank line here and similar locations.


> +enum inv_icm45686_gyro_fs {
> +	INV_ICM45686_GYRO_FS_4000DPS,
> +	INV_ICM45686_GYRO_FS_2000DPS,
> +	INV_ICM45686_GYRO_FS_1000DPS,
> +	INV_ICM45686_GYRO_FS_500DPS,
> +	INV_ICM45686_GYRO_FS_250DPS,
> +	INV_ICM45686_GYRO_FS_125DPS,
> +	INV_ICM45686_GYRO_FS_62_5DPS,
> +	INV_ICM45686_GYRO_FS_31_25DPS,
> +	INV_ICM45686_GYRO_FS_15_625DPS,
> +	INV_ICM45686_GYRO_FS_NB,
> +};
> +
> +/* accelerometer fullscale values */
> +enum inv_icm45600_accel_fs {
> +	INV_ICM45600_ACCEL_FS_16G,
> +	INV_ICM45600_ACCEL_FS_8G,
> +	INV_ICM45600_ACCEL_FS_4G,
> +	INV_ICM45600_ACCEL_FS_2G,
> +	INV_ICM45600_ACCEL_FS_NB,
> +};
> +enum inv_icm45686_accel_fs {
> +	INV_ICM45686_ACCEL_FS_32G,
> +	INV_ICM45686_ACCEL_FS_16G,
> +	INV_ICM45686_ACCEL_FS_8G,
> +	INV_ICM45686_ACCEL_FS_4G,
> +	INV_ICM45686_ACCEL_FS_2G,
> +	INV_ICM45686_ACCEL_FS_NB,
> +};
> +
> +/* ODR suffixed by LN or LP are Low-Noise or Low-Power mode only */
> +enum inv_icm45600_odr {
> +	INV_ICM45600_ODR_6400HZ_LN = 0x03,
> +	INV_ICM45600_ODR_3200HZ_LN,
> +	INV_ICM45600_ODR_1600HZ_LN,
> +	INV_ICM45600_ODR_800HZ_LN,
> +	INV_ICM45600_ODR_400HZ,
> +	INV_ICM45600_ODR_200HZ,
> +	INV_ICM45600_ODR_100HZ,
> +	INV_ICM45600_ODR_50HZ,
> +	INV_ICM45600_ODR_25HZ,
> +	INV_ICM45600_ODR_12_5HZ,
> +	INV_ICM45600_ODR_6_25HZ_LP,
> +	INV_ICM45600_ODR_3_125HZ_LP,
> +	INV_ICM45600_ODR_1_5625HZ_LP,
> +	INV_ICM45600_ODR_NB,
> +};
> +
> +struct inv_icm45600_sensor_conf {
> +	int mode;
> +	int fs;
> +	int odr;
> +	int filter;
> +};
> +#define INV_ICM45600_SENSOR_CONF_INIT		{-1, -1, -1, -1}
> +
> +struct inv_icm45600_conf {
> +	struct inv_icm45600_sensor_conf gyro;
> +	struct inv_icm45600_sensor_conf accel;
> +};
> +
> +struct inv_icm45600_suspended {
> +	enum inv_icm45600_sensor_mode gyro;
> +	enum inv_icm45600_sensor_mode accel;
> +};
> +
> +/**
> + *  struct inv_icm45600_state - driver state variables
> + *  @lock:		lock for serializing multiple registers access.
> + *  @chip:		chip identifier.
> + *  @name:		chip name.

If this is the state (Rather than config data) why do you need a chip
name here?  iio_dev->name is perhaps enough?


> + *  @map:		regmap pointer.
> + *  @vdd_supply:	VDD voltage regulator for the chip.
Seems this is always on, so no need to store in this structure

> + *  @vddio_supply:	I/O voltage regulator for the chip.
This does need to be here though as you turn it off when suspended.

> + *  @orientation:	sensor chip orientation relative to main hardware.
> + *  @conf:		chip sensors configurations.
> + *  @suspended:		suspended sensors configuration.
> + *  @indio_gyro:	gyroscope IIO device.
> + *  @indio_accel:	accelerometer IIO device.
> + *  @timestamp:		interrupt timestamps.
> + *  @fifo:		FIFO management structure.
> + *  @buffer:		data transfer buffer aligned for DMA.
> + */
> +struct inv_icm45600_state {
> +	struct mutex lock;
> +	enum inv_icm45600_chip chip;
> +	const char *name;
> +	struct regmap *map;
> +	struct regulator *vdd_supply;
> +	struct regulator *vddio_supply;
> +	struct iio_mount_matrix orientation;
> +	struct inv_icm45600_conf conf;
> +	struct inv_icm45600_suspended suspended;
> +	struct iio_dev *indio_gyro;
> +	struct iio_dev *indio_accel;
> +	struct {
> +		int64_t gyro;
> +		int64_t accel;
> +	} timestamp;
> +	struct inv_icm45600_fifo fifo;
> +	uint8_t buffer[2] __aligned(IIO_DMA_MINALIGN);
> +};
> +

1 blank line enough

> +
> +/**
> + * struct inv_icm45600_sensor_state - sensor state variables
> + * @scales:		table of scales.
> + * @scales_len:		length (nb of items) of the scales table.
> + * @power_mode:		sensor requested power mode (for common frequencies)
> + * @ts:			timestamp module states.
> + */
> +struct inv_icm45600_sensor_state {
> +	const int *scales;
> +	size_t scales_len;
> +	enum inv_icm45600_sensor_mode power_mode;
> +	struct inv_sensors_timestamp ts;
> +};
> +
> +/* Virtual register addresses: @bank on MSB (16 bits), @address on LSB */
> +
> +/* Indirect register access */
> +#define INV_ICM45600_REG_IREG_ADDR			0x7C
> +#define INV_ICM45600_REG_IREG_DATA			0x7E
> +
> +/* Direct acces registers */
> +#define INV_ICM45600_REG_MISC2				0x007F
> +#define INV_ICM45600_MISC2_SOFT_RESET			BIT(1)
> +
> +#define INV_ICM45600_REG_DRIVE_CONFIG0			0x0032
> +#define INV_ICM45600_DRIVE_CONFIG0_I2C_MASK		GENMASK(6, 4)
> +#define INV_ICM45600_DRIVE_CONFIG0_I2C(_rate)		\
These 'function' like macros don't really add anything to redability over
just calling the FIELD_PREP inline.

> +		FIELD_PREP(INV_ICM45600_DRIVE_CONFIG0_I2C_MASK, (_rate))
> +#define INV_ICM45600_I2C_SLEW_RATE_7NS				\
> +		INV_ICM45600_DRIVE_CONFIG0_I2C(2)
For these store the value that get written to the filed, not the result
of the FIELD_PREP. 

> +#define INV_ICM45600_I2C_SLEW_RATE_20NS				\
> +		INV_ICM45600_DRIVE_CONFIG0_I2C(0)
> +#define INV_ICM45600_DRIVE_CONFIG0_SPI_MASK		GENMASK(3, 1)
> +#define INV_ICM45600_DRIVE_CONFIG0_SPI(_rate)		\
> +		FIELD_PREP(INV_ICM45600_DRIVE_CONFIG0_SPI_MASK, (_rate))

Similar to above.  Just have the masks and field values defined, not
the result of using FIELD_PREP() which you should just do inline in the code.

Same for all the other similar places.

> +#define INV_ICM45600_SPI_SLEW_RATE_0_5NS			\
> +		INV_ICM45600_DRIVE_CONFIG0_SPI(6)

> +
> +/* Sleep times required by the driver */
> +#define INV_ICM45600_POWER_UP_TIME_MS		100

If these are not used in lots of places, just use the value inline but
provide a reference there to the spec to say why the value is what it
is.

> +#define INV_ICM45600_RESET_TIME_MS		1
> +#define INV_ICM45600_ACCEL_STARTUP_TIME_MS	60
> +#define INV_ICM45600_GYRO_STARTUP_TIME_MS	60
> +#define INV_ICM45600_GYRO_STOP_TIME_MS		150
> +#define INV_ICM45600_SUSPEND_DELAY_MS		2000
> +#define INV_ICM45600_IREG_DELAY_US		4



> diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..3c046cad83474da43509295dd5542e40b7a0296a
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c
> @@ -0,0 +1,902 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2025 Invensense, Inc.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/common/inv_sensors_timestamp.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/kfifo_buf.h>
> +#include <linux/kernel.h>
> +#include <linux/math64.h>
> +#include <linux/mutex.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +
> +#include "inv_icm45600_buffer.h"
> +#include "inv_icm45600_temp.h"
> +#include "inv_icm45600.h"
> +
> +#define INV_ICM45600_ACCEL_CHAN(_modifier, _index, _ext_info)		\

I'd put these define nearer where it is used.  That is where we will want
to know how it is defined.

> +	{								\
> +		.type = IIO_ACCEL,					\
> +		.modified = 1,						\
> +		.channel2 = _modifier,					\
> +		.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_type_available =			\
> +			BIT(IIO_CHAN_INFO_SCALE) |			\
> +			BIT(IIO_CHAN_INFO_CALIBBIAS),			\
> +		.info_mask_shared_by_all =				\
> +			BIT(IIO_CHAN_INFO_SAMP_FREQ),			\
> +		.info_mask_shared_by_all_available =			\
> +			BIT(IIO_CHAN_INFO_SAMP_FREQ),			\
> +		.scan_index = _index,					\
> +		.scan_type = {						\
> +			.sign = 's',					\
> +			.realbits = 16,					\
> +			.storagebits = 16,				\
> +			.endianness = IIO_LE,				\
> +		},							\
> +		.ext_info = _ext_info,					\
> +	}


> +
> +static int inv_icm45600_accel_power_mode_set(struct iio_dev *indio_dev,
> +					     const struct iio_chan_spec *chan,
> +					     unsigned int idx)
> +{
> +	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
> +	unsigned int val = 0;
> +	int power_mode;
> +
> +	if (chan->type != IIO_ACCEL)
> +		return -EINVAL;
> +
> +	if (idx >= ARRAY_SIZE(inv_icm45600_accel_power_mode_values))

Given you provided the enum, this probably doesn't need checking. This function
can't be called with a value that doesn't pass the check that I can see.

> +		return -EINVAL;
> +
> +	if (iio_buffer_enabled(indio_dev))
This is racey and why we have to use
	if (!iio_device_claim_direct(indio_deV))
		return -EBUSY;

and release it explicitly when we don't mind the state changing again.

> +		return -EBUSY;
> +
> +	power_mode = inv_icm45600_accel_power_mode_values[idx];
> +
> +	guard(mutex)(&st->lock);
> +
> +	/* prevent change if power mode is not supported by the ODR */
> +	switch (power_mode) {
> +	case INV_ICM45600_SENSOR_MODE_LOW_NOISE:
> +		if (st->conf.accel.odr >= INV_ICM45600_ODR_6_25HZ_LP &&
> +		    st->conf.accel.odr <= INV_ICM45600_ODR_1_5625HZ_LP)
> +			return -EPERM;
> +		break;
> +	case INV_ICM45600_SENSOR_MODE_LOW_POWER:
> +	default:
The enum only takes two values. Use that as the type and the defualt here
should not be needed.

> +		if (st->conf.accel.odr <= INV_ICM45600_ODR_800HZ_LN)
> +			return -EPERM;
> +		break;
> +	}
> +
> +	accel_st->power_mode = power_mode;
> +
> +	if (accel_st->power_mode == INV_ICM45600_SENSOR_MODE_LOW_POWER)
> +		val = INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL;
> +
> +	return regmap_update_bits(st->map, INV_ICM45600_REG_SMC_CONTROL_0,
> +				INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL, val);
> +}
> +
> +static int inv_icm45600_accel_power_mode_get(struct iio_dev *indio_dev,
> +					     const struct iio_chan_spec *chan)
> +{
> +	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
> +	unsigned int idx;
> +	int power_mode;
> +
> +	if (chan->type != IIO_ACCEL)
> +		return -EINVAL;
> +
> +	guard(mutex)(&st->lock);
> +
> +	/* if sensor is on, returns actual power mode and not configured one */
> +	switch (st->conf.accel.mode) {
> +	case INV_ICM45600_SENSOR_MODE_LOW_POWER:
> +	case INV_ICM45600_SENSOR_MODE_LOW_NOISE:
> +		power_mode = st->conf.accel.mode;
> +		break;
> +	default:
> +		power_mode = accel_st->power_mode;
> +		break;
> +	}
> +
> +	for (idx = 0; idx < ARRAY_SIZE(inv_icm45600_accel_power_mode_values); ++idx) {
> +		if (power_mode == inv_icm45600_accel_power_mode_values[idx])
> +			break;
> +	}
> +	if (idx >= ARRAY_SIZE(inv_icm45600_accel_power_mode_values))

It's not going to >  Just check ==

> +		return -EINVAL;
> +
> +	return idx;
> +}
> +
> +static const struct iio_enum inv_icm45600_accel_power_mode_enum = {
> +	.items = inv_icm45600_accel_power_mode_items,
> +	.num_items = ARRAY_SIZE(inv_icm45600_accel_power_mode_items),
> +	.set = inv_icm45600_accel_power_mode_set,
> +	.get = inv_icm45600_accel_power_mode_get,
> +};
> +
> +static const struct iio_chan_spec_ext_info inv_icm45600_accel_ext_infos[] = {
> +	IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm45600_get_mount_matrix),
> +	IIO_ENUM_AVAILABLE("power_mode", IIO_SHARED_BY_TYPE,
> +			   &inv_icm45600_accel_power_mode_enum),
> +	IIO_ENUM("power_mode", IIO_SHARED_BY_TYPE,
> +		 &inv_icm45600_accel_power_mode_enum),
> +	{},
	{ }

for all such terminators in IIO.  There is a patch on list from David
fixing all of the existing ones so we finally make this consistent.

> +};
> +
> +static const struct iio_chan_spec inv_icm45600_accel_channels[] = {
> +	INV_ICM45600_ACCEL_CHAN(IIO_MOD_X, INV_ICM45600_ACCEL_SCAN_X,
> +				inv_icm45600_accel_ext_infos),
> +	INV_ICM45600_ACCEL_CHAN(IIO_MOD_Y, INV_ICM45600_ACCEL_SCAN_Y,
> +				inv_icm45600_accel_ext_infos),
> +	INV_ICM45600_ACCEL_CHAN(IIO_MOD_Z, INV_ICM45600_ACCEL_SCAN_Z,
> +				inv_icm45600_accel_ext_infos),
> +	INV_ICM45600_TEMP_CHAN(INV_ICM45600_ACCEL_SCAN_TEMP),
> +	IIO_CHAN_SOFT_TIMESTAMP(INV_ICM45600_ACCEL_SCAN_TIMESTAMP),
> +};
> +
> +/*
> + * IIO buffer data: size must be a power of 2 and timestamp aligned
> + * 16 bytes: 6 bytes acceleration, 2 bytes temperature, 8 bytes timestamp
> + */
> +struct inv_icm45600_accel_buffer {
> +	struct inv_icm45600_fifo_sensor_data accel;
> +	int16_t temp;
> +	aligned_s64 timestamp;
> +};
> +
> +#define INV_ICM45600_SCAN_MASK_ACCEL_3AXIS				\
> +	(BIT(INV_ICM45600_ACCEL_SCAN_X) |				\
> +	BIT(INV_ICM45600_ACCEL_SCAN_Y) |				\
> +	BIT(INV_ICM45600_ACCEL_SCAN_Z))
This doesn't add anything over just using all these BIT() in the one
entry in *_scan_masks[]

Always look at a define and check whether it actually improves readability enough
to bother.


> +
> +#define INV_ICM45600_SCAN_MASK_TEMP	BIT(INV_ICM45600_ACCEL_SCAN_TEMP)
> +
> +static const unsigned long inv_icm45600_accel_scan_masks[] = {
> +	/* 3-axis accel + temperature */
> +	INV_ICM45600_SCAN_MASK_ACCEL_3AXIS | INV_ICM45600_SCAN_MASK_TEMP,
> +	0,
> +};
> +

> +
> +static int inv_icm45600_accel_read_sensor(struct iio_dev *indio_dev,
> +					  struct iio_chan_spec const *chan,
> +					  int16_t *val)
> +{
> +	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
> +	struct device *dev = regmap_get_device(st->map);
> +	struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_INIT;
> +	unsigned int reg;
> +	__le16 *data;
> +	int ret;
> +
> +	if (chan->type != IIO_ACCEL)
> +		return -EINVAL;
> +
> +	switch (chan->channel2) {
> +	case IIO_MOD_X:
> +		reg = INV_ICM45600_REG_ACCEL_DATA_X;
> +		break;
> +	case IIO_MOD_Y:
> +		reg = INV_ICM45600_REG_ACCEL_DATA_Y;
> +		break;
> +	case IIO_MOD_Z:
> +		reg = INV_ICM45600_REG_ACCEL_DATA_Z;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	pm_runtime_get_sync(dev);
> +	scoped_guard(mutex, &st->lock) {
> +		/* enable accel sensor */
> +		conf.mode = accel_st->power_mode;
> +		ret = inv_icm45600_set_accel_conf(st, &conf, NULL);
> +		if (ret)
> +			break;
> +
> +		/* read accel register data */
> +		data = (__le16 *)&st->buffer[0];

Consider a union so you can use st->buffer16 when you need the __le16 intepretation
of that buffer.

> +		ret = regmap_bulk_read(st->map, reg, data, sizeof(*data));
> +		if (ret)
> +			break;
> +
> +		*val = (int16_t)le16_to_cpup(data);

Slight preference for sign_extend32() instead of casting to 16 bit int.
Same affect but kind of more obvious what is going on.

> +		if (*val == INV_ICM45600_DATA_INVALID)
> +			ret = -EINVAL;
> +	}
> +	pm_runtime_mark_last_busy(dev);
> +	pm_runtime_put_autosuspend(dev);
> +	return ret;
> +}
> +
> +/* IIO format int + nano */
> +static const int inv_icm45600_accel_scale[] = {

Often we handle these as a 2D array and cast to a 1D one at the
point of use.  Simplifies the indices.

> +	/* +/- 16G => 0.004788403 m/s-2 */
> +	[2 * INV_ICM45600_ACCEL_FS_16G] = 0,
> +	[2 * INV_ICM45600_ACCEL_FS_16G + 1] = 4788403,
> +	/* +/- 8G => 0.002394202 m/s-2 */
> +	[2 * INV_ICM45600_ACCEL_FS_8G] = 0,
> +	[2 * INV_ICM45600_ACCEL_FS_8G + 1] = 2394202,
> +	/* +/- 4G => 0.001197101 m/s-2 */
> +	[2 * INV_ICM45600_ACCEL_FS_4G] = 0,
> +	[2 * INV_ICM45600_ACCEL_FS_4G + 1] = 1197101,
> +	/* +/- 2G => 0.000598550 m/s-2 */
> +	[2 * INV_ICM45600_ACCEL_FS_2G] = 0,
> +	[2 * INV_ICM45600_ACCEL_FS_2G + 1] = 598550,
> +};
> +static const int inv_icm45686_accel_scale[] = {
> +	/* +/- 32G => 0.009576806 m/s-2 */
> +	[2 * INV_ICM45686_ACCEL_FS_32G] = 0,
> +	[2 * INV_ICM45686_ACCEL_FS_32G + 1] = 9576806,
> +	/* +/- 16G => 0.004788403 m/s-2 */
> +	[2 * INV_ICM45686_ACCEL_FS_16G] = 0,
> +	[2 * INV_ICM45686_ACCEL_FS_16G + 1] = 4788403,
> +	/* +/- 8G => 0.002394202 m/s-2 */
> +	[2 * INV_ICM45686_ACCEL_FS_8G] = 0,
> +	[2 * INV_ICM45686_ACCEL_FS_8G + 1] = 2394202,
> +	/* +/- 4G => 0.001197101 m/s-2 */
> +	[2 * INV_ICM45686_ACCEL_FS_4G] = 0,
> +	[2 * INV_ICM45686_ACCEL_FS_4G + 1] = 1197101,
> +	/* +/- 2G => 0.000598550 m/s-2 */
> +	[2 * INV_ICM45686_ACCEL_FS_2G] = 0,
> +	[2 * INV_ICM45686_ACCEL_FS_2G + 1] = 598550,
> +};

> +
> +static int inv_icm45600_accel_write_scale(struct iio_dev *indio_dev,
> +					  int val, int val2)
> +{
> +	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
> +	struct device *dev = regmap_get_device(st->map);
> +	unsigned int idx;
> +	struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_INIT;
> +	int ret;
> +
> +	for (idx = 0; idx < accel_st->scales_len; idx += 2) {
> +		if (val == accel_st->scales[idx] &&
> +		    val2 == accel_st->scales[idx + 1])
> +			break;
> +	}
> +	if (idx >= accel_st->scales_len)

As in earlier case. Don't imply it can be > when it can only match.

> +		return -EINVAL;
> +
> +	conf.fs = idx / 2;
> +
> +	/* Full scale register starts at 1 for not High FSR parts */
> +	if (accel_st->scales == inv_icm45600_accel_scale)
> +		conf.fs++;
> +
> +	pm_runtime_get_sync(dev);
> +	scoped_guard(mutex, &st->lock) {
> +		ret = inv_icm45600_set_accel_conf(st, &conf, NULL);
> +	}
> +	pm_runtime_mark_last_busy(dev);
> +	pm_runtime_put_autosuspend(dev);
> +
> +	return ret;
> +}
> +
> +/* IIO format int + micro */
> +static const int inv_icm45600_accel_odr[] = {

Similar to above, consider casting form 2D array so we can have
it easy to read here. Lots of drivers do that for their available attributes.

> +	/* 1.5625Hz */
> +	1, 562500,
> +	/* 3.125Hz */
> +	3, 125000,
> +	/* 6.25Hz */
> +	6, 250000,
> +	/* 12.5Hz */
> +	12, 500000,
> +	/* 25Hz */
> +	25, 0,
> +	/* 50Hz */
> +	50, 0,
> +	/* 100Hz */
> +	100, 0,
> +	/* 200Hz */
> +	200, 0,
> +	/* 400Hz */
> +	400, 0,
> +	/* 800Hz */
> +	800, 0,
> +	/* 1.6kHz */
> +	1600, 0,
> +	/* 3.2kHz */
> +	3200, 0,
> +	/* 6.4kHz */
> +	6400, 0,
> +};
> +
> +static const int inv_icm45600_accel_odr_conv[] = {
> +	INV_ICM45600_ODR_1_5625HZ_LP,
> +	INV_ICM45600_ODR_3_125HZ_LP,
> +	INV_ICM45600_ODR_6_25HZ_LP,
> +	INV_ICM45600_ODR_12_5HZ,
> +	INV_ICM45600_ODR_25HZ,
> +	INV_ICM45600_ODR_50HZ,
> +	INV_ICM45600_ODR_100HZ,
> +	INV_ICM45600_ODR_200HZ,
> +	INV_ICM45600_ODR_400HZ,
> +	INV_ICM45600_ODR_800HZ_LN,
> +	INV_ICM45600_ODR_1600HZ_LN,
> +	INV_ICM45600_ODR_3200HZ_LN,
> +	INV_ICM45600_ODR_6400HZ_LN,
> +};


> +struct iio_dev *inv_icm45600_accel_init(struct inv_icm45600_state *st)
> +{
> +	struct device *dev = regmap_get_device(st->map);
> +	const char *name;
> +	struct inv_icm45600_sensor_state *accel_st;
> +	struct inv_sensors_timestamp_chip ts_chip;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +
> +	name = devm_kasprintf(dev, GFP_KERNEL, "%s-accel", st->name);
> +	if (!name)
> +		return ERR_PTR(-ENOMEM);
> +
> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*accel_st));
> +	if (!indio_dev)
> +		return ERR_PTR(-ENOMEM);
> +	accel_st = iio_priv(indio_dev);
> +
> +	switch (st->chip) {

This stuff should be data in a chip specific structure not code here.

	accel_st->scales = st->chip_info->scales;
etc.


> +	case INV_CHIP_ICM45686:
> +	case INV_CHIP_ICM45688P:
> +	case INV_CHIP_ICM45689:
> +	case INV_CHIP_ICM45687:
> +		accel_st->scales = inv_icm45686_accel_scale;
> +		accel_st->scales_len = ARRAY_SIZE(inv_icm45686_accel_scale);
> +		break;
> +	default:
> +		accel_st->scales = inv_icm45600_accel_scale;
> +		accel_st->scales_len = ARRAY_SIZE(inv_icm45600_accel_scale);
> +		/* Set Accel default FSR */
> +		ret = regmap_update_bits(st->map, INV_ICM45600_REG_ACCEL_CONFIG0,
> +					INV_ICM45600_ACCEL_CONFIG0_FS_MASK,
> +					INV_ICM45600_ACCEL_CONFIG0_FS_16G);
> +		if (ret)
> +			return ERR_PTR(ret);
> +		break;
> +	}
> +	/* low-power (LP) mode by default at init, no ULP mode */
> +	accel_st->power_mode = INV_ICM45600_SENSOR_MODE_LOW_POWER;
> +	ret = regmap_set_bits(st->map, INV_ICM45600_REG_SMC_CONTROL_0,
> +			      INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	/*
> +	 * clock period is 32kHz (31250ns)
> +	 * jitter is +/- 2% (20 per mille)
> +	 */
> +	ts_chip.clock_period = 31250;
> +	ts_chip.jitter = 20;
> +	ts_chip.init_period = inv_icm45600_odr_to_period(st->conf.accel.odr);
> +	inv_sensors_timestamp_init(&accel_st->ts, &ts_chip);
> +
> +	iio_device_set_drvdata(indio_dev, st);
> +	indio_dev->name = name;
> +	indio_dev->info = &inv_icm45600_accel_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = inv_icm45600_accel_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(inv_icm45600_accel_channels);
> +	indio_dev->available_scan_masks = inv_icm45600_accel_scan_masks;
> +
> +	ret = devm_iio_kfifo_buffer_setup(dev, indio_dev,
> +					  &inv_icm45600_buffer_ops);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	ret = devm_iio_device_register(dev, indio_dev);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	return indio_dev;
> +}
> +
> +int inv_icm45600_accel_parse_fifo(struct iio_dev *indio_dev)
> +{
> +	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
> +	struct inv_sensors_timestamp *ts = &accel_st->ts;
> +	ssize_t i, size;
> +	unsigned int no;
> +	const void *accel, *gyro, *timestamp;

Can't we type these? Using void * is far from ideal from a readability
point of view.

> +	const int8_t *temp;
> +	unsigned int odr;
> +	int64_t ts_val;
> +	struct inv_icm45600_accel_buffer buffer;
> +
> +	/* parse all fifo packets */
> +	for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) {
> +		size = inv_icm45600_fifo_decode_packet(&st->fifo.data[i],
> +				&accel, &gyro, &temp, &timestamp, &odr);
> +		/* quit if error or FIFO is empty */
> +		if (size <= 0)
> +			return size;
> +
> +		/* skip packet if no accel data or data is invalid */
> +		if (accel == NULL || !inv_icm45600_fifo_is_data_valid(accel))
> +			continue;
> +
> +		/* update odr */
> +		if (odr & INV_ICM45600_SENSOR_ACCEL)
> +			inv_sensors_timestamp_apply_odr(ts, st->fifo.period,
> +							 st->fifo.nb.total, no);
> +
> +		/* buffer is copied to userspace, zeroing it to avoid any data leak */
> +		memset(&buffer, 0, sizeof(buffer));
> +		memcpy(&buffer.accel, accel, sizeof(buffer.accel));
> +		/* convert 8 bits FIFO temperature in high resolution format */
> +		buffer.temp = temp ? (*temp * 64) : 0;
> +		ts_val = inv_sensors_timestamp_pop(ts);
> +		iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val);
> +	}
> +
> +	return 0;
> +}
> diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..66e9826005a9fecf5b154c0a1308a4c4f40c4edd
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c
> @@ -0,0 +1,906 @@

> +
> +static int inv_icm45600_write(void *context, const void *data,
> +				   size_t count)
> +{
> +	uint8_t *d = (uint8_t *)data;
> +	unsigned int reg = (unsigned int) be16_to_cpup(data);
> +	struct regmap *map = context;
> +
> +	if (INV_ICM45600_REG_GET_BANK(reg) == 0)
> +		return regmap_bulk_write(map, INV_ICM45600_REG_GET_ADDR(reg),
> +					d+2, count-2);
> +
> +	return inv_icm45600_ireg_write(map, reg, d+2, count-2);
> +}
single blank line.
> +
> +
> +static const struct regmap_bus inv_icm45600_regmap_bus = {
> +	.read = inv_icm45600_read,

> +
> +static const struct inv_icm45600_hw inv_icm45600_hw[INV_CHIP_NB] = {

These arrays have tended to prove a design mistake in other drivers.
Instead just have extern in the header so the individual bus drivers
can reference each structure instance in their match tables.

> +	[INV_CHIP_ICM45605] = {
> +		.whoami = INV_ICM45600_WHOAMI_ICM45605,
> +		.name = "icm45605",
> +		.conf = &inv_icm45600_default_conf,
> +	},
> +	[INV_CHIP_ICM45686] = {
> +		.whoami = INV_ICM45600_WHOAMI_ICM45686,
> +		.name = "icm45686",
> +		.conf = &inv_icm45686_default_conf,
> +	},
> +	[INV_CHIP_ICM45688P] = {
> +		.whoami = INV_ICM45600_WHOAMI_ICM45688P,
> +		.name = "icm45688p",
> +		.conf = &inv_icm45686_default_conf,
> +	},
> +	[INV_CHIP_ICM45608] = {
> +		.whoami = INV_ICM45600_WHOAMI_ICM45608,
> +		.name = "icm45608",
> +		.conf = &inv_icm45600_default_conf,
> +	},
> +	[INV_CHIP_ICM45634] = {
> +		.whoami = INV_ICM45600_WHOAMI_ICM45634,
> +		.name = "icm45634",
> +		.conf = &inv_icm45600_default_conf,
> +	},
> +	[INV_CHIP_ICM45689] = {
> +		.whoami = INV_ICM45600_WHOAMI_ICM45689,
> +		.name = "icm45689",
> +		.conf = &inv_icm45686_default_conf,
> +	},
> +	[INV_CHIP_ICM45606] = {
> +		.whoami = INV_ICM45600_WHOAMI_ICM45606,
> +		.name = "icm45606",
> +		.conf = &inv_icm45600_default_conf,
> +	},
> +	[INV_CHIP_ICM45687] = {
> +		.whoami = INV_ICM45600_WHOAMI_ICM45687,
> +		.name = "icm45687",
> +		.conf = &inv_icm45686_default_conf,
> +	},
> +};
> +
>
> +
> +int inv_icm45600_set_gyro_conf(struct inv_icm45600_state *st,
> +			       struct inv_icm45600_sensor_conf *conf,
> +			       unsigned int *sleep_ms)
> +{
> +	struct inv_icm45600_sensor_conf *oldconf = &st->conf.gyro;
> +	unsigned int val;
> +	int ret;
> +
> +	/* sanitize missing values with current values */
> +	if (conf->mode < 0)
> +		conf->mode = oldconf->mode;
> +	if (conf->fs < 0)
> +		conf->fs = oldconf->fs;
> +	if (conf->odr < 0)
> +		conf->odr = oldconf->odr;
> +	if (conf->filter < 0)
> +		conf->filter = oldconf->filter;
> +
> +	/* force power mode against ODR when sensor is on */
> +	switch (conf->mode) {
> +	case INV_ICM45600_SENSOR_MODE_LOW_POWER:
> +		if (conf->odr != INV_ICM45600_ODR_400HZ)
> +			conf->filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X;
> +		else
> +			conf->filter = INV_ICM45600_GYRO_LP_AVG_SEL_2X;
> +		if (conf->odr <= INV_ICM45600_ODR_800HZ_LN)
> +			conf->mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE;
> +		break;
> +	case INV_ICM45600_SENSOR_MODE_LOW_NOISE:
> +		if (conf->odr >= INV_ICM45600_ODR_6_25HZ_LP &&
> +			   conf->odr <= INV_ICM45600_ODR_1_5625HZ_LP) {
> +			conf->mode = INV_ICM45600_SENSOR_MODE_LOW_POWER;
> +			conf->filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X;
> +		}
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	/* set GYRO_CONFIG0 register (gyro fullscale & odr) */
> +	if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) {
> +		val = INV_ICM45600_GYRO_CONFIG0_FS(conf->fs) |
> +		      INV_ICM45600_GYRO_CONFIG0_ODR(conf->odr);
> +		ret = regmap_write(st->map, INV_ICM45600_REG_GYRO_CONFIG0, val);
> +		if (ret)
> +			return ret;
> +		oldconf->fs = conf->fs;
> +		oldconf->odr = conf->odr;
> +	}
> +
> +	/* set GYRO_LP_AVG_SEL register (gyro low-power average filter) */
> +	if (conf->filter != oldconf->filter) {
> +		ret = regmap_update_bits(st->map, INV_ICM45600_IPREG_SYS1_REG_170,
> +			INV_ICM45600_IPREG_SYS1_REG_170_MASK, conf->filter);
> +		if (ret)
> +			return ret;
> +		oldconf->filter = conf->filter;
> +	}
> +
> +	/* set PWR_MGMT0 register (gyro sensor mode) */
> +	return inv_icm45600_set_pwr_mgmt0(st, conf->mode, st->conf.accel.mode,
> +					  sleep_ms);
> +
> +	return 0;
can't get here.

> +}

> +
> +static void inv_icm45600_disable_pm(void *_data)
> +{
> +	struct device *dev = _data;
> +
> +	pm_runtime_put_sync(dev);
This put makes me wonder what it is balancing. 
> +	pm_runtime_disable(dev);
This one should be handled by devm_pm_runtime_enable()
though that has some issues we are working out with some power domains.

> +}
> +
> +int inv_icm45600_core_probe(struct regmap *regmap, int chip, bool reset,
> +			    inv_icm45600_bus_setup bus_setup)
> +{
> +	struct device *dev = regmap_get_device(regmap);
> +	struct fwnode_handle *fwnode;
> +	struct inv_icm45600_state *st;
> +	struct regmap *regmap_custom;
> +	int irq, irq_type;
> +	bool open_drain;
> +	int ret;
> +
> +	if (chip <= INV_CHIP_INVALID || chip >= INV_CHIP_NB) {
We should never all this function if we can't guarantee those.
I don't like the enum as stated above but this check is down
to the use of int chip rather than enum in the parameters.
(+ the invalid thing that we don't need anyway that I can see).

> +		dev_err(dev, "invalid chip = %d\n", chip);
For all probe related error prints please use
		return dev_err_probe(dev, -ENODEV, "...")
It uses fewer lines and where deferral can happen deals with it cleanly
plus sets up some debug stuff.

> +		return -ENODEV;
> +	}
> +
> +	/* get INT1 only supported interrupt */
> +	fwnode = dev_fwnode(dev);
> +	if (!fwnode)
> +		return -ENODEV;
> +	irq = fwnode_irq_get_byname(fwnode, "INT1");
> +	if (irq < 0) {
> +		if (irq != -EPROBE_DEFER)
> +			dev_err(dev, "error missing INT1 interrupt\n");
> +		return irq;
> +	}
> +
> +	irq_type = irq_get_trigger_type(irq);
> +	if (!irq_type)
> +		irq_type = IRQF_TRIGGER_FALLING;

This should never happen unless we have a bug in the firmware so
shouldn't need the fallback.

> +
> +	open_drain = device_property_read_bool(dev, "drive-open-drain");
> +
> +	regmap_custom = devm_regmap_init(dev, &inv_icm45600_regmap_bus,
> +					 regmap, &inv_icm45600_regmap_config);
> +	if (IS_ERR(regmap_custom)) {
> +		dev_err(dev, "Failed to register icm45600 regmap %ld\n", PTR_ERR(regmap_custom));
> +		return PTR_ERR(regmap_custom);
> +	}
> +
> +	st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
> +	if (!st)
> +		return -ENOMEM;
> +
> +	dev_set_drvdata(dev, st);
> +	mutex_init(&st->lock);
	ret = devm_mutex_init(); 
	if (ret)
		return ret;

We are doing this for new code as it provides small debug advantages and with
devm doesn't add much complexity.
 
> +	st->chip = chip;
> +	st->map = regmap_custom;
> +
> +	ret = iio_read_mount_matrix(dev, &st->orientation);
> +	if (ret) {
> +		dev_err(dev, "failed to retrieve mounting matrix %d\n", ret);
> +		return ret;
> +	}
> +
> +	st->vdd_supply = devm_regulator_get(dev, "vdd");

devm_regulator_get_enabled() and no need to store in the state
structure or use a custom devm callback.

> +	if (IS_ERR(st->vdd_supply))
> +		return PTR_ERR(st->vdd_supply);
> +
> +	st->vddio_supply = devm_regulator_get(dev, "vddio");
> +	if (IS_ERR(st->vddio_supply))
> +		return PTR_ERR(st->vddio_supply);
> +
> +	ret = regulator_enable(st->vdd_supply);
> +	if (ret)
> +		return ret;
> +	msleep(INV_ICM45600_POWER_UP_TIME_MS);
> +
> +	ret = devm_add_action_or_reset(dev, inv_icm45600_disable_vdd_reg, st);
> +	if (ret)
> +		return ret;
> +
> +	ret = inv_icm45600_enable_regulator_vddio(st);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_add_action_or_reset(dev, inv_icm45600_disable_vddio_reg, st);
> +	if (ret)
> +		return ret;
> +
> +	/* setup chip registers */
> +	ret = inv_icm45600_setup(st, reset, bus_setup);
> +	if (ret)
> +		return ret;
> +
> +	ret = inv_icm45600_timestamp_setup(st);
> +	if (ret)
> +		return ret;
> +
> +	ret = inv_icm45600_buffer_init(st);
> +	if (ret)
> +		return ret;
> +
> +	st->indio_gyro = inv_icm45600_gyro_init(st);
> +	if (IS_ERR(st->indio_gyro))
> +		return PTR_ERR(st->indio_gyro);
> +
> +	st->indio_accel = inv_icm45600_accel_init(st);
> +	if (IS_ERR(st->indio_accel))
> +		return PTR_ERR(st->indio_accel);
> +
> +	ret = inv_icm45600_irq_init(st, irq, irq_type, open_drain);
> +	if (ret)
> +		return ret;
> +
> +	/* setup runtime power management */
> +	ret = pm_runtime_set_active(dev);
> +	if (ret)
> +		return ret;
> +	pm_runtime_get_noresume(dev);
> +	pm_runtime_enable(dev);

devm_pm_runtime_enable() which will correctly turn off autosuspend (unlike this
code).

> +	pm_runtime_set_autosuspend_delay(dev, INV_ICM45600_SUSPEND_DELAY_MS);
> +	pm_runtime_use_autosuspend(dev);
> +	pm_runtime_put(dev);
> +
> +	return devm_add_action_or_reset(dev, inv_icm45600_disable_pm, dev);
> +}
> +EXPORT_SYMBOL_NS_GPL(inv_icm45600_core_probe, "IIO_ICM45600");


> +/* Sensors are enabled by iio devices, no need to turn them back on here. */
> +static int inv_icm45600_runtime_resume(struct device *dev)
> +{
> +	struct inv_icm45600_state *st = dev_get_drvdata(dev);
> +	int ret;
> +
> +	guard(mutex)(&st->lock);
> +
> +	ret = inv_icm45600_enable_regulator_vddio(st);
return inv_icm...

> +
> +	return ret;
> +}

> diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..4ada93f990c1ffdc1e0a00ae7c78e03c09d1c682
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c

I assumed this was similar to accelerometer and for this version won't read it.



> diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_temp.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_temp.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..b2b5697da407de0a0338841eb858b4322996923c
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_temp.c
> @@ -0,0 +1,82 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2025 Invensense, Inc.
> + */
Push this into the core file.  It doesn't need it's own place to live
given how simple it is.

> +
> +#include <linux/device.h>
> +#include <linux/iio/iio.h>
> +#include <linux/kernel.h>
> +#include <linux/mutex.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +
> +#include "inv_icm45600_temp.h"
> +#include "inv_icm45600.h"
> +
> +static int inv_icm45600_temp_read(struct inv_icm45600_state *st, int16_t *temp)
> +{
> +	struct device *dev = regmap_get_device(st->map);
> +	__le16 *raw;
> +	int ret;
> +
> +	pm_runtime_get_sync(dev);
> +	scoped_guard(mutex, &st->lock) {
> +		raw = (__le16 *)&st->buffer[0];
> +		ret = regmap_bulk_read(st->map, INV_ICM45600_REG_TEMP_DATA, raw, sizeof(*raw));
> +		if (ret)
> +			break;
> +
> +		*temp = (int16_t)le16_to_cpup(raw);
> +		if (*temp == INV_ICM45600_DATA_INVALID)
> +			ret = -EINVAL;
> +	}
> +	pm_runtime_mark_last_busy(dev);
> +	pm_runtime_put_autosuspend(dev);
> +
> +	return ret;
> +}
> +
> +int inv_icm45600_temp_read_raw(struct iio_dev *indio_dev,
> +			       struct iio_chan_spec const *chan,
> +			       int *val, int *val2, long mask)
> +{
> +	struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
> +	int16_t temp;
> +	int ret;
> +
> +	if (chan->type != IIO_TEMP)
> +		return -EINVAL;
> +
> +	/* temperature sensor work only with accel and/or gyro */
> +	if (st->conf.accel.mode <= INV_ICM45600_SENSOR_MODE_STANDBY &&
> +		st->conf.gyro.mode  <= INV_ICM45600_SENSOR_MODE_STANDBY) {
> +		return -ENODATA;
> +	}
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		if (!iio_device_claim_direct(indio_dev))
> +			return -EBUSY;
> +		ret = inv_icm45600_temp_read(st, &temp);
> +		iio_device_release_direct(indio_dev);
> +		if (ret)
> +			return ret;
> +		*val = temp;
> +		return IIO_VAL_INT;
> +	/*
> +	 * T°C = (temp / 128) + 25
> +	 * Tm°C = 1000 * ((temp * 100 / 12800) + 25)
> +	 * scale: 100000 / 13248 = 7.8125
> +	 * offset: 25000
> +	 */
> +	case IIO_CHAN_INFO_SCALE:
> +		*val = 7;
> +		*val2 = 812500;
> +		return IIO_VAL_INT_PLUS_MICRO;
> +	case IIO_CHAN_INFO_OFFSET:
> +		*val = 25000;
> +		return IIO_VAL_INT;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_temp.h b/drivers/iio/imu/inv_icm45600/inv_icm45600_temp.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..1b93e1417e2ec1292e44f05b98c6393354c5297c
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_temp.h
> @@ -0,0 +1,31 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2025 Invensense, Inc.
> + */
> +
> +#ifndef INV_ICM45600_TEMP_H_
> +#define INV_ICM45600_TEMP_H_
> +
> +#include <linux/iio/iio.h>
> +
Put this in the core header.

> +#define INV_ICM45600_TEMP_CHAN(_index)					\
> +	{								\
> +		.type = IIO_TEMP,					\
> +		.info_mask_separate =					\
> +			BIT(IIO_CHAN_INFO_RAW) |			\
> +			BIT(IIO_CHAN_INFO_OFFSET) |			\
> +			BIT(IIO_CHAN_INFO_SCALE),			\
> +		.scan_index = _index,					\
> +		.scan_type = {						\
> +			.sign = 's',					\
> +			.realbits = 16,					\
> +			.storagebits = 16,				\
> +			.endianness = IIO_LE,				\
> +		},							\
> +	}
> +
> +int inv_icm45600_temp_read_raw(struct iio_dev *indio_dev,
> +			       struct iio_chan_spec const *chan,
> +			       int *val, int *val2, long mask);
> +
> +#endif
> 


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

* Re: [PATCH 5/8] iio: imu: inv_icm45600: add buffer support in iio devices
  2025-04-11 13:28 ` [PATCH 5/8] iio: imu: inv_icm45600: add buffer support in iio devices Remi Buisson via B4 Relay
@ 2025-04-12 19:13   ` Jonathan Cameron
  2025-04-12 19:15     ` Andy Shevchenko
  0 siblings, 1 reply; 19+ messages in thread
From: Jonathan Cameron @ 2025-04-12 19:13 UTC (permalink / raw)
  To: Remi Buisson via B4 Relay
  Cc: remi.buisson, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel,
	linux-iio, devicetree

On Fri, 11 Apr 2025 13:28:37 +0000
Remi Buisson via B4 Relay <devnull+remi.buisson.tdk.com@kernel.org> wrote:

> From: Remi Buisson <remi.buisson@tdk.com>
> 
> Add all FIFO parsing and reading functions. Add accel and gyro
> kfifo buffer and FIFO data parsing. Use device interrupt for
> reading data FIFO and launching accel and gyro parsing.
> 
> Support hwfifo watermark by multiplexing gyro and accel settings.
> Support hwfifo flush.
> 
> Signed-off-by: Remi Buisson <remi.buisson@tdk.com>
I'm out of time and energy today. So just one trivial thing inline.
I'll look more closely at v2 after the patches have been reorganized
to build up features as you go.

Thanks,

Jonathan

> +
> +/**
> + * inv_icm45600_buffer_update_watermark - update watermark FIFO threshold
> + * @st:	driver internal state
> + *
> + * Returns 0 on success, a negative error code otherwise.
> + *
> + * FIFO watermark threshold is computed based on the required watermark values
> + * set for gyro and accel sensors. Since watermark is all about acceptable data
> + * latency, use the smallest setting between the 2. It means choosing the
> + * smallest latency but this is not as simple as choosing the smallest watermark
> + * value. Latency depends on watermark and ODR. It requires several steps:
> + * 1) compute gyro and accel latencies and choose the smallest value.
> + * 2) adapt the chosen latency so that it is a multiple of both gyro and accel
> + *    ones. Otherwise it is possible that you don't meet a requirement. (for
> + *    example with gyro @100Hz wm 4 and accel @100Hz with wm 6, choosing the
> + *    value of 4 will not meet accel latency requirement because 6 is not a
> + *    multiple of 4. You need to use the value 2.)
> + * 3) Since all periods are multiple of each others, watermark is computed by
> + *    dividing this computed latency by the smallest period, which corresponds
> + *    to the FIFO frequency.
> + */
> +int inv_icm45600_buffer_update_watermark(struct inv_icm45600_state *st)
> +{

> +	raw_wm = INV_ICM45600_FIFO_WATERMARK_VAL(watermark);
> +	memcpy(st->buffer, &raw_wm, sizeof(raw_wm));
> +	ret = regmap_bulk_write(st->map, INV_ICM45600_REG_FIFO_WATERMARK,
> +				st->buffer, sizeof(raw_wm));
> +	if (ret)
> +		return ret;
> +
> +	return 0;
trivial but return regmap_bulk_write()

> +}


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

* Re: [PATCH 5/8] iio: imu: inv_icm45600: add buffer support in iio devices
  2025-04-12 19:13   ` Jonathan Cameron
@ 2025-04-12 19:15     ` Andy Shevchenko
  0 siblings, 0 replies; 19+ messages in thread
From: Andy Shevchenko @ 2025-04-12 19:15 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Remi Buisson via B4 Relay, remi.buisson, David Lechner,
	Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-kernel, linux-iio, devicetree

On Sat, Apr 12, 2025 at 10:14 PM Jonathan Cameron <jic23@kernel.org> wrote:
> On Fri, 11 Apr 2025 13:28:37 +0000
> Remi Buisson via B4 Relay <devnull+remi.buisson.tdk.com@kernel.org> wrote:

...

> > +     raw_wm = INV_ICM45600_FIFO_WATERMARK_VAL(watermark);
> > +     memcpy(st->buffer, &raw_wm, sizeof(raw_wm));
> > +     ret = regmap_bulk_write(st->map, INV_ICM45600_REG_FIFO_WATERMARK,
> > +                             st->buffer, sizeof(raw_wm));
> > +     if (ret)
> > +             return ret;
> > +
> > +     return 0;
> trivial but return regmap_bulk_write()

Actually looking at this I think it should have proper __be16/__le16
(or what is there?) types and use respective putter-getter instead of
memcpy().

-- 
With Best Regards,
Andy Shevchenko

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

end of thread, other threads:[~2025-04-12 19:16 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-11 13:28 [PATCH 0/8] iio: imu: new inv_icm45600 driver Remi Buisson via B4 Relay
2025-04-11 13:28 ` [PATCH 1/8] iio: imu: inv_icm45600: add " Remi Buisson via B4 Relay
2025-04-12 19:10   ` Jonathan Cameron
2025-04-11 13:28 ` [PATCH 2/8] iio: imu: inv_icm45600: add I2C driver for " Remi Buisson via B4 Relay
2025-04-11 19:21   ` Andy Shevchenko
2025-04-11 13:28 ` [PATCH 3/8] iio: imu: inv_icm45600: add SPI " Remi Buisson via B4 Relay
2025-04-11 13:28 ` [PATCH 4/8] iio: imu: inv_icm45600: add I3C " Remi Buisson via B4 Relay
2025-04-11 13:28 ` [PATCH 5/8] iio: imu: inv_icm45600: add buffer support in iio devices Remi Buisson via B4 Relay
2025-04-12 19:13   ` Jonathan Cameron
2025-04-12 19:15     ` Andy Shevchenko
2025-04-11 13:28 ` [PATCH 6/8] iio: imu: add Kconfig and Makefile for inv_icm45600 driver Remi Buisson via B4 Relay
2025-04-11 17:36   ` Jonathan Cameron
2025-04-11 13:28 ` [PATCH 7/8] dt-bindings: iio: imu: Add inv_icm45600 documentation Remi Buisson via B4 Relay
2025-04-11 14:39   ` Rob Herring (Arm)
2025-04-11 21:16   ` Rob Herring
2025-04-11 21:18   ` David Lechner
2025-04-12 18:17     ` Jonathan Cameron
2025-04-12 18:24   ` Jonathan Cameron
2025-04-11 13:28 ` [PATCH 8/8] MAINTAINERS: add entry for inv_icm45600 6-axis imu sensor Remi Buisson 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;
as well as URLs for NNTP newsgroup(s).