iio: imu: inv_icm45600: add new inv_icm45600 driver

Core component of a new driver for InvenSense ICM-45600 devices.
It includes registers definition, main probe/setup, and device
utility functions.

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>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
This commit is contained in:
Remi Buisson
2025-10-07 07:20:03 +00:00
committed by Jonathan Cameron
parent 7d55d00185
commit 7ff021a3fa
6 changed files with 963 additions and 0 deletions

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
config INV_ICM45600
tristate
select IIO_INV_SENSORS_TIMESTAMP

View File

@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-or-later
obj-$(CONFIG_INV_ICM45600) += inv-icm45600.o
inv-icm45600-y += inv_icm45600_core.o

View File

@@ -0,0 +1,331 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (C) 2025 Invensense, Inc. */
#ifndef INV_ICM45600_H_
#define INV_ICM45600_H_
#include <linux/bits.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/sizes.h>
#include <linux/types.h>
#include <linux/iio/common/inv_sensors_timestamp.h>
#include <linux/iio/iio.h>
#define INV_ICM45600_REG_BANK_MASK GENMASK(15, 8)
#define INV_ICM45600_REG_ADDR_MASK GENMASK(7, 0)
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_MAX
};
/* 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_MAX
};
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_MAX
};
/* 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_MAX
};
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_MAX
};
/* 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_MAX
};
struct inv_icm45600_sensor_conf {
u8 mode;
u8 fs;
u8 odr;
u8 filter;
};
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_chip_info {
u8 whoami;
const char *name;
const struct inv_icm45600_conf *conf;
};
/**
* struct inv_icm45600_state - driver state variables
* @lock: lock for serializing multiple registers access.
* @map: regmap pointer.
* @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.
* @chip_info: chip driver data.
* @timestamp: interrupt timestamps.
* @buffer: data transfer buffer aligned for DMA.
*/
struct inv_icm45600_state {
struct mutex lock;
struct regmap *map;
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;
const struct inv_icm45600_chip_info *chip_info;
struct {
s64 gyro;
s64 accel;
} timestamp;
union {
u8 buff[2];
__le16 u16;
u8 ireg[3];
} buffer __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_SPI_MASK GENMASK(3, 1)
#define INV_ICM45600_SPI_SLEW_RATE_0_5NS 6
#define INV_ICM45600_SPI_SLEW_RATE_4NS 5
#define INV_ICM45600_SPI_SLEW_RATE_5NS 4
#define INV_ICM45600_SPI_SLEW_RATE_7NS 3
#define INV_ICM45600_SPI_SLEW_RATE_10NS 2
#define INV_ICM45600_SPI_SLEW_RATE_14NS 1
#define INV_ICM45600_SPI_SLEW_RATE_38NS 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 0
#define INV_ICM45600_FIFO_CONFIG0_MODE_STREAM 1
#define INV_ICM45600_FIFO_CONFIG0_MODE_STOP_ON_FULL 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_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_MASK GENMASK(3, 2)
#define INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK GENMASK(1, 0)
#define INV_ICM45600_REG_ACCEL_CONFIG0 0x001B
#define INV_ICM45600_ACCEL_CONFIG0_FS_MASK GENMASK(6, 4)
#define INV_ICM45600_ACCEL_CONFIG0_ODR_MASK GENMASK(3, 0)
#define INV_ICM45600_REG_GYRO_CONFIG0 0x001C
#define INV_ICM45600_GYRO_CONFIG0_FS_MASK GENMASK(7, 4)
#define INV_ICM45600_GYRO_CONFIG0_ODR_MASK GENMASK(3, 0)
#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
/* FIFO is configured for 8kb */
#define INV_ICM45600_FIFO_SIZE_MAX SZ_8K
#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
#define INV_ICM45600_GYRO_OFFUSER_MASK GENMASK(13, 0)
/* Gyro Averaging filter */
#define INV_ICM45600_IPREG_SYS1_REG_170 0xA4AA
#define INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK GENMASK(4, 1)
#define INV_ICM45600_GYRO_LP_AVG_SEL_8X 5
#define INV_ICM45600_GYRO_LP_AVG_SEL_2X 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
#define INV_ICM45600_ACCEL_OFFUSER_MASK GENMASK(13, 0)
/* Accel averaging filter */
#define INV_ICM45600_IPREG_SYS2_REG_129 0xA581
#define INV_ICM45600_ACCEL_LP_AVG_SEL_1X 0x0000
#define INV_ICM45600_ACCEL_LP_AVG_SEL_4X 0x0002
/* Sleep times required by the driver */
#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_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);
u32 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,
const struct inv_icm45600_chip_info *chip_info,
bool reset, inv_icm45600_bus_setup bus_setup);
#endif

View File

@@ -0,0 +1,621 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* Copyright (C) 2025 Invensense, Inc. */
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/limits.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/time.h>
#include <linux/types.h>
#include <asm/byteorder.h>
#include <linux/iio/iio.h>
#include "inv_icm45600.h"
static int inv_icm45600_ireg_read(struct regmap *map, unsigned int reg,
u8 *data, size_t count)
{
const struct device *dev = regmap_get_device(map);
struct inv_icm45600_state *st = dev_get_drvdata(dev);
unsigned int d;
size_t i;
int ret;
st->buffer.ireg[0] = FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg);
st->buffer.ireg[1] = FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg);
/* Burst write address. */
ret = regmap_bulk_write(map, INV_ICM45600_REG_IREG_ADDR, st->buffer.ireg, 2);
/*
* Wait while the device is busy processing the address.
* Datasheet: 13.3 MINIMUM WAIT TIME-GAP
*/
fsleep(INV_ICM45600_IREG_DELAY_US);
if (ret)
return ret;
/* Read the data. */
for (i = 0; i < count; i++) {
ret = regmap_read(map, INV_ICM45600_REG_IREG_DATA, &d);
/*
* Wait while the device is busy processing the address.
* Datasheet: 13.3 MINIMUM WAIT TIME-GAP
*/
fsleep(INV_ICM45600_IREG_DELAY_US);
if (ret)
return ret;
data[i] = d;
}
return 0;
}
static int inv_icm45600_ireg_write(struct regmap *map, unsigned int reg,
const u8 *data, size_t count)
{
const struct device *dev = regmap_get_device(map);
struct inv_icm45600_state *st = dev_get_drvdata(dev);
size_t i;
int ret;
st->buffer.ireg[0] = FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg);
st->buffer.ireg[1] = FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg);
st->buffer.ireg[2] = data[0];
/* Burst write address and first byte. */
ret = regmap_bulk_write(map, INV_ICM45600_REG_IREG_ADDR, st->buffer.ireg, 3);
/*
* Wait while the device is busy processing the address.
* Datasheet: 13.3 MINIMUM WAIT TIME-GAP
*/
fsleep(INV_ICM45600_IREG_DELAY_US);
if (ret)
return ret;
/* Write the remaining bytes. */
for (i = 1; i < count; i++) {
ret = regmap_write(map, INV_ICM45600_REG_IREG_DATA, data[i]);
/*
* Wait while the device is busy processing the address.
* Datasheet: 13.3 MINIMUM WAIT TIME-GAP
*/
fsleep(INV_ICM45600_IREG_DELAY_US);
if (ret)
return ret;
}
return 0;
}
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 = be16_to_cpup(reg_buf);
struct regmap *map = context;
if (FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg))
return inv_icm45600_ireg_read(map, reg, val_buf, val_size);
return regmap_bulk_read(map, FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg),
val_buf, val_size);
}
static int inv_icm45600_write(void *context, const void *data, size_t count)
{
const u8 *d = data;
unsigned int reg = be16_to_cpup(data);
struct regmap *map = context;
if (FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg))
return inv_icm45600_ireg_write(map, reg, d + 2, count - 2);
return regmap_bulk_write(map, FIELD_GET(INV_ICM45600_REG_ADDR_MASK, 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,
};
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;
}
u32 inv_icm45600_odr_to_period(enum inv_icm45600_odr odr)
{
static const u32 odr_periods[INV_ICM45600_ODR_MAX] = {
/* 3 first values are reserved, left to 0 */
[INV_ICM45600_ODR_6400HZ_LN] = 156250,
[INV_ICM45600_ODR_3200HZ_LN] = 312500,
[INV_ICM45600_ODR_1600HZ_LN] = 625000,
[INV_ICM45600_ODR_800HZ_LN] = 1250000,
[INV_ICM45600_ODR_400HZ] = 2500000,
[INV_ICM45600_ODR_200HZ] = 5000000,
[INV_ICM45600_ODR_100HZ] = 10000000,
[INV_ICM45600_ODR_50HZ] = 20000000,
[INV_ICM45600_ODR_25HZ] = 40000000,
[INV_ICM45600_ODR_12_5HZ] = 80000000,
[INV_ICM45600_ODR_6_25HZ_LP] = 160000000,
[INV_ICM45600_ODR_3_125HZ_LP] = 320000000,
[INV_ICM45600_ODR_1_5625HZ_LP] = 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 = FIELD_PREP(INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK, gyro) |
FIELD_PREP(INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK, 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 the required wait time for sensors to stabilize. */
sleepval = 0;
if (accel != oldaccel && oldaccel == INV_ICM45600_SENSOR_MODE_OFF)
sleepval = max(sleepval, INV_ICM45600_ACCEL_STARTUP_TIME_MS);
if (gyro != oldgyro) {
if (oldgyro == INV_ICM45600_SENSOR_MODE_OFF)
sleepval = max(sleepval, INV_ICM45600_GYRO_STARTUP_TIME_MS);
else if (gyro == INV_ICM45600_SENSOR_MODE_OFF)
sleepval = max(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;
}
static void inv_icm45600_set_default_conf(struct inv_icm45600_sensor_conf *conf,
struct inv_icm45600_sensor_conf *oldconf)
{
/* Sanitize missing values with current values. */
if (conf->mode == U8_MAX)
conf->mode = oldconf->mode;
if (conf->fs == U8_MAX)
conf->fs = oldconf->fs;
if (conf->odr == U8_MAX)
conf->odr = oldconf->odr;
if (conf->filter == U8_MAX)
conf->filter = oldconf->filter;
}
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;
inv_icm45600_set_default_conf(conf, oldconf);
/* Force the power mode against the ODR when sensor is on. */
if (conf->mode > INV_ICM45600_SENSOR_MODE_STANDBY) {
if (conf->odr <= INV_ICM45600_ODR_800HZ_LN) {
conf->mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE;
} else {
conf->mode = INV_ICM45600_SENSOR_MODE_LOW_POWER;
/* sanitize averaging value depending on ODR for low-power mode */
/* maximum 1x @400Hz */
if (conf->odr == INV_ICM45600_ODR_400HZ)
conf->filter = INV_ICM45600_ACCEL_LP_AVG_SEL_1X;
else
conf->filter = INV_ICM45600_ACCEL_LP_AVG_SEL_4X;
}
}
/* Set accel fullscale & odr. */
if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) {
val = FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_FS_MASK, conf->fs) |
FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_ODR_MASK, 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 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;
}
/* Update the sensor accel 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;
inv_icm45600_set_default_conf(conf, oldconf);
/* Force the power mode against ODR when sensor is on. */
if (conf->mode > INV_ICM45600_SENSOR_MODE_STANDBY) {
if (conf->odr >= INV_ICM45600_ODR_6_25HZ_LP) {
conf->mode = INV_ICM45600_SENSOR_MODE_LOW_POWER;
conf->filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X;
} else {
conf->mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE;
}
}
/* Set gyro fullscale & odr. */
if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) {
val = FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_FS_MASK, conf->fs) |
FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_ODR_MASK, 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 low-power average filter. */
if (conf->filter != oldconf->filter) {
val = FIELD_PREP(INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK, conf->filter);
ret = regmap_update_bits(st->map, INV_ICM45600_IPREG_SYS1_REG_170,
INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK, val);
if (ret)
return ret;
oldconf->filter = conf->filter;
}
/* Update the sensor gyro mode. */
return inv_icm45600_set_pwr_mgmt0(st, conf->mode, st->conf.accel.mode,
sleep_ms);
}
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);
guard(mutex)(&st->lock);
if (readval)
return regmap_read(st->map, reg, readval);
else
return regmap_write(st->map, reg, writeval);
}
static int inv_icm45600_set_conf(struct inv_icm45600_state *st,
const struct inv_icm45600_conf *conf)
{
unsigned int val;
int ret;
val = FIELD_PREP(INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK, conf->gyro.mode) |
FIELD_PREP(INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK, conf->accel.mode);
ret = regmap_write(st->map, INV_ICM45600_REG_PWR_MGMT0, val);
if (ret)
return ret;
val = FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_FS_MASK, conf->gyro.fs) |
FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_ODR_MASK, conf->gyro.odr);
ret = regmap_write(st->map, INV_ICM45600_REG_GYRO_CONFIG0, val);
if (ret)
return ret;
val = FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_FS_MASK, conf->accel.fs) |
FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_ODR_MASK, conf->accel.odr);
ret = regmap_write(st->map, INV_ICM45600_REG_ACCEL_CONFIG0, val);
if (ret)
return ret;
/* Save configuration. */
st->conf = *conf;
return 0;
}
/**
* inv_icm45600_setup() - check and setup chip
* @st: driver internal state
* @chip_info: detected chip description
* @reset: define whether a reset is required or not
* @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,
const struct inv_icm45600_chip_info *chip_info,
bool reset, inv_icm45600_bus_setup bus_setup)
{
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 != chip_info->whoami) {
/*
* SPI interface has no ack mechanism.
* 0xFF or 0x00 whoami means no response from the device.
*/
if (val == U8_MAX || val == 0)
return dev_err_probe(dev, -ENODEV,
"Invalid whoami %#02x expected %#02x (%s)\n",
val, chip_info->whoami, chip_info->name);
dev_warn(dev, "Unexpected whoami %#02x expected %#02x (%s)\n",
val, chip_info->whoami, chip_info->name);
}
st->chip_info = chip_info;
if (reset) {
/* Reset previous state. */
ret = regmap_write(st->map, INV_ICM45600_REG_MISC2,
INV_ICM45600_MISC2_SOFT_RESET);
if (ret)
return ret;
/*
* IMU reset time.
* Datasheet: 16.84 REG_MISC2
*/
fsleep(USEC_PER_MSEC);
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, chip_info->conf);
}
static int inv_icm45600_timestamp_setup(struct inv_icm45600_state *st)
{
/* Enable timestamps. */
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.
* Duration is empirically defined.
*/
fsleep(3 * USEC_PER_MSEC);
return 0;
}
static void inv_icm45600_disable_vddio_reg(void *_data)
{
struct inv_icm45600_state *st = _data;
struct device *dev = regmap_get_device(st->map);
if (pm_runtime_status_suspended(dev))
return;
regulator_disable(st->vddio_supply);
}
int inv_icm45600_core_probe(struct regmap *regmap, const struct inv_icm45600_chip_info *chip_info,
bool reset, inv_icm45600_bus_setup bus_setup)
{
struct device *dev = regmap_get_device(regmap);
struct inv_icm45600_state *st;
struct regmap *regmap_custom;
int ret;
regmap_custom = devm_regmap_init(dev, &inv_icm45600_regmap_bus, regmap,
&inv_icm45600_regmap_config);
if (IS_ERR(regmap_custom))
return dev_err_probe(dev, PTR_ERR(regmap_custom), "Failed to register regmap\n");
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
if (!st)
return -ENOMEM;
dev_set_drvdata(dev, st);
ret = devm_mutex_init(dev, &st->lock);
if (ret)
return ret;
st->map = regmap_custom;
ret = iio_read_mount_matrix(dev, &st->orientation);
if (ret)
return dev_err_probe(dev, ret, "Failed to retrieve mounting matrix\n");
st->vddio_supply = devm_regulator_get(dev, "vddio");
if (IS_ERR(st->vddio_supply))
return PTR_ERR(st->vddio_supply);
ret = devm_regulator_get_enable(dev, "vdd");
if (ret)
return dev_err_probe(dev, ret, "Failed to get vdd regulator\n");
/*
* Supply ramp time + Start-up time.
* Datasheet: 3.3.2 A.C. Electrical Characteristics
*/
fsleep(5 * USEC_PER_MSEC);
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;
ret = inv_icm45600_setup(st, chip_info, reset, bus_setup);
if (ret)
return ret;
ret = inv_icm45600_timestamp_setup(st);
if (ret)
return ret;
ret = devm_pm_runtime_set_active_enabled(dev);
if (ret)
return ret;
pm_runtime_get_noresume(dev);
pm_runtime_set_autosuspend_delay(dev, 2 * USEC_PER_MSEC);
pm_runtime_use_autosuspend(dev);
pm_runtime_put(dev);
return 0;
}
EXPORT_SYMBOL_NS_GPL(inv_icm45600_core_probe, "IIO_ICM45600");
/*
* Suspend saves sensors state and turns everything off.
*/
static int inv_icm45600_suspend(struct device *dev)
{
struct inv_icm45600_state *st = dev_get_drvdata(dev);
scoped_guard(mutex, &st->lock) {
/* Save sensors states */
st->suspended.gyro = st->conf.gyro.mode;
st->suspended.accel = st->conf.accel.mode;
}
return pm_runtime_force_suspend(dev);
}
/*
* 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);
int ret;
ret = pm_runtime_force_resume(dev);
if (ret)
return ret;
scoped_guard(mutex, &st->lock) {
/* Restore sensors state. */
ret = inv_icm45600_set_pwr_mgmt0(st, st->suspended.gyro,
st->suspended.accel, NULL);
}
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 0;
}
/* 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);
guard(mutex)(&st->lock);
return inv_icm45600_enable_regulator_vddio(st);
}
EXPORT_NS_GPL_DEV_PM_OPS(inv_icm45600_pm_ops, IIO_ICM45600) = {
SYSTEM_SLEEP_PM_OPS(inv_icm45600_suspend, inv_icm45600_resume)
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");